{"id":13602131,"url":"https://github.com/instant-dev/api","last_synced_at":"2025-04-12T20:43:45.065Z","repository":{"id":198378665,"uuid":"699558265","full_name":"instant-dev/api","owner":"instant-dev","description":"Instant API: Build type-safe web APIs with JavaScript","archived":false,"fork":false,"pushed_at":"2025-03-22T05:19:55.000Z","size":2309,"stargazers_count":252,"open_issues_count":1,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-04T00:08:49.099Z","etag":null,"topics":["api","api-gateway","bun","deno","gateway","javascript","nodejs","server"],"latest_commit_sha":null,"homepage":"https://instant.dev","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/instant-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-02T21:48:40.000Z","updated_at":"2025-03-31T04:51:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"9607ccec-7933-4798-94cf-bafa444111b3","html_url":"https://github.com/instant-dev/api","commit_stats":{"total_commits":476,"total_committers":8,"mean_commits":59.5,"dds":"0.23949579831932777","last_synced_commit":"26d4499b114e74ee197bc5fac2782f86136749c6"},"previous_names":["instant-dev/api"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instant-dev%2Fapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instant-dev%2Fapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instant-dev%2Fapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instant-dev%2Fapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/instant-dev","download_url":"https://codeload.github.com/instant-dev/api/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248631685,"owners_count":21136555,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","api-gateway","bun","deno","gateway","javascript","nodejs","server"],"created_at":"2024-08-01T18:01:14.876Z","updated_at":"2025-04-12T20:43:45.041Z","avatar_url":"https://github.com/instant-dev.png","language":"JavaScript","readme":"# Instant API\n\n![travis-ci build](https://travis-ci.org/instant-dev/api.svg?branch=main)\n![npm version](https://img.shields.io/npm/v/@instant.dev/api?label=)\n\n# Build type-safe web APIs with JavaScript, instantly\n## Spec generation and LLM streaming\n\n**Instant API** is a framework for building APIs with JavaScript that implements\n**type-safety at the HTTP interface**. By doing so, it eliminates the need for\nschema validation libraries entirely. Simply write a JSDoc-compliant comment\nblock for a function that represents your API endpoint and stop worrying about\nvalidation, sanitization and testing for user input. The OpenAPI specification\nfor your API is then automatically generated in both JSON and YAML at\n`localhost:8000/.well-known/openapi.json` and\n`localhost:8000/.well-known/openapi.yaml`, respectively.\n\nAdditionally, Instant API comes a number of features optimized for integrations\nwith LLMs and chat bots:\n\n- First class support for Server-Sent Events using `text/event-stream` makes streaming LLM responses easy\n- [LLM function calling](https://openai.com/blog/function-calling-and-other-api-updates) can be integrated\neasily via JSON schema output at `localhost:8000/.well-known/schema.json`\n- Experimental auto-generation of `localhost:8000/.well-known/ai-plugin.json`\n- The ability to instantly return `200 OK` responses and execute in the background for Slack, Discord webhooks\n\nYou will find Instant API is a very full-featured framework despite being an\nearly release. It has been in development for six years as the engine behind the\n[Autocode](https://autocode.com) serverless platform where it has horizontally scaled\nto handle over 100M API requests per day.\n\n## Quick example: Standard API\n\nHere's an example API endpoint built with Instant API. It would be available\nat the URL `example.com/v1/weather/current` via HTTP GET. It has length\nrestrictions on `location`, range restrictions on `coords.lat` and `coords.lng`,\nand `tags` is an array of string. The `@returns` definitions ensure that the API\ncontract with the user is upheld: if the wrong data is returned an error will be\nthrown.\n\nFile: `/functions/v1/weather/current.mjs`\n\n```javascript\n/**\n * Retrieve the weather for a specific location\n * @param {?string{1..64}}   location    Search by location\n * @param {?object}          coords      Provide specific latitude and longitude\n * @param {number{-90,90}}   coords.lat  Latitude\n * @param {number{-180,180}} coords.lng  Longitude\n * @param {string[]}         tags        Nearby locations to include\n * @returns {object} weather             Your weather result\n * @returns {number} weather.temperature Current tmperature of the location\n * @returns {string} weather.unit        Fahrenheit or Celsius\n */\nexport async function GET (location = null, coords = null, tags = []) {\n\n  if (!location \u0026\u0026 !coords) {\n    // Prefixing an error message with a \"###:\" between 400 and 404\n    //   automatically creates the correct client error:\n    //     BadRequestError, UnauthorizedError, PaymentRequiredError,\n    //     ForbiddenError, NotFoundError\n    // Otherwise, will throw a RuntimeError with code 420\n    throw new Error(`400: Must provide either location or coords`);\n  } else if (location \u0026\u0026 coords) {\n    throw new Error(`400: Can not provide both location and coords`);\n  }\n\n  // Fetch your own API data\n  await getSomeWeatherDataFor(location, coords, tags);\n\n  // mock a response\n  return {\n    temperature: 89.2\n    units: `°F`\n  };\n\n}\n```\n\n## Quick example: LLM Streaming\n\nLLM streaming is simple. It relies on a special `context` object and defining\n`@stream` parameters to create a `text/event-stream` response. You can think\nof `@stream` as similar to `@returns`, where you're specifying the schema\nfor the output to the user. If this contract is broken, your API will throw an\nerror. In order to send a stream to the user, we add a special `context` object\nto the API footprint as the last parameter and use an exposed `context.stream()`\nmethod.\n\nFile: `/functions/v1/ai-helper.mjs`\n\n```javascript\nimport OpenAI from 'openai';\nconst openai = new OpenAI(process.env.OPENAI_API_KEY);\n\n/**\n * Streams results for our lovable assistant\n * @param {string} query The question for our assistant\n * @stream {object}   chunk\n * @stream {string}   chunk.id\n * @stream {string}   chunk.object\n * @stream {integer}  chunk.created\n * @stream {string}   chunk.model\n * @stream {object[]} chunk.choices\n * @stream {integer}  chunk.choices[].index\n * @stream {object}   chunk.choices[].delta\n * @stream {?string}  chunk.choices[].delta.role\n * @stream {?string}  chunk.choices[].delta.content\n * @returns {object} message\n * @returns {string} message.content\n */\nexport async function GET (query, context) {\n  const completion = await openai.chat.completions.create({\n    messages: [\n      {role: `system`, content: `You are a lovable, cute assistant that uses too many emojis.`},\n      {role: `user`, content: query}\n    ],\n    model: `gpt-3.5-turbo`,\n    stream: true\n  });\n  const messages = [];\n  for await (const chunk of completion) {\n    // Stream our response as text/event-stream when ?_stream parameter added\n    context.stream('chunk', chunk); // chunk has the schema provided above\n    messages.push(chunk?.choices?.[0]?.delta?.content || '');\n  }\n  return {content: messages.join('')};\n};\n```\n\nBy default, this method will return something like;\n\n```json\n{\n  \"content\": \"Hey there! 💁‍♀️ I'm doing great, thank you! 💖✨ How about you? 😊🌈\"\n}\n```\n\nHowever, if you append `?_stream` to query parameters or `{\"_stream\": true}` to\nbody parameters, it will turn into a `text/event-stream` with your `context.stream()`\nevents sandwiched between a `@begin` and `@response` event. The `@response` event\nwill be an object containing the details of what the HTTP response would have contained\nhad the API call been made normally.\n\n```shell\nid: 2023-10-25T04:29:59.115000000Z/2e7c7860-4a66-4824-98fa-a7cf71946f19\nevent: @begin\ndata: \"2023-10-25T04:29:59.115Z\"\n\n[... more events ...]\n\nevent: chunk\ndata: {\"id\":\"chatcmpl-8DPoluIgN4TDIuE1usFOKTLPiIUbQ\",\"object\":\"chat.completion.chunk\",\"created\":1698208199,\"model\":\"gpt-3.5-turbo-0613\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" 💯\"},\"finish_reason\":null}]}\n\n[... more events ...]\n\nevent: @response\ndata: {\"statusCode\":200,\"headers\":{\"X-Execution-Uuid\":\"2e7c7860-4a66-4824-98fa-a7cf71946f19\",\"X-Instant-Api\":\"true\",\"Access-Control-Allow-Origin\":\"*\",\"Access-Control-Allow-Methods\":\"GET, POST, OPTIONS, HEAD, PUT, DELETE\",\"Access-Control-Allow-Headers\":\"\",\"Access-Control-Expose-Headers\":\"x-execution-uuid, x-instant-api, access-control-allow-origin, access-control-allow-methods, access-control-allow-headers, x-execution-uuid\",\"Content-Type\":\"application/json\"},\"body\":\"{\\\"content\\\":\\\"Hey there! 🌞 I'm feeling 💯 today! Full of energy and ready to help you out. How about you? How are you doing? 🌈😊\\\"}\"}\n```\n\n## Table of Contents\n\n1. [Getting Started](#getting-started)\n   1. [Quickstart](#quickstart)\n   1. [Custom installation](#custom-installation)\n1. [Endpoints and Type Safety](#endpoints-and-type-safety)\n   1. [Creating Endpoints](#creating-endpoints)\n   1. [Responding to HTTP methods](#responding-to-http-methods)\n      1. [Endpoint lifecycle](#endpoint-lifecycle)\n      1. [Typing your endpoint](#typing-your-endpoint)\n          1. [Undocumented parameters](#undocumented-parameters)\n          1. [Required parameters](#required-parameters)\n          1. [Optional parameters](#optional-parameters)\n      1. [`context` object](#context-object)\n      1. [API endpoints: `functions/` directory](#api-endpoints-functions-directory)\n         1. [Index routing with `index.mjs`](#index-routing-with-indexmjs)\n         1. [Subdirectory routing with `404.mjs`](#subdirectory-routing-with-404mjs)\n      1. [Static files: `www/` directory](#static-files-www-directory)\n         1. [Index routing with `index.html`](#index-routing-with-indexhtml)\n         1. [Subdirectory routing with `404.html`](#subdirectory-routing-with-404html)\n   1. [Type Safety](#type-safety)\n      1. [Supported types](#supported-types)\n      1. [Type coercion](#type-coercion)\n      1. [Combining types](#combining-types)\n      1. [Enums and restricting to specific values](#enums-and-restricting-to-specific-values)\n      1. [Sizes (lengths)](#sizes-lengths)\n      1. [Ranges](#ranges)\n      1. [Arrays](#arrays)\n      1. [Object schemas](#object-schemas)\n   1. [Parameter validation](#parameter-validation)\n      1. [Query and Body parsing with `application/x-www-form-urlencoded`](#query-and-body-parsing-with-applicationx-www-form-urlencoded)\n      1. [Query vs. Body parameters](#query-vs-body-parameters)\n   1. [CORS (Cross-Origin Resource Sharing)](#cors-cross-origin-resource-sharing)\n   1. [Returning responses](#returning-responses)\n      1. [`@returns` type safety](#returns-type-safety)\n      1. [Error responses](#error-responses)\n      1. [Custom HTTP responses](#custom-http-responses)\n      1. [Returning files with Buffer responses](#returning-files-with-buffer-responses)\n      1. [Streaming responses](#streaming-responses)\n      1. [Debug responses](#debug-responses)\n   1. [Throwing errors](#throwing-errors)\n1. [OpenAPI Specification Generation](#openapi-specification-generation)\n   1. [OpenAPI Output Example](#openapi-output-example)\n   1. [JSON Schema Output Example](#json-schema-output-example)\n   1. [Hiding endpoints with `@private`](#hiding-endpoints-with-private)\n1. [Streaming and LLM Support](#streaming-and-llm-support)\n   1. [`@stream` type safety](#stream-type-safety)\n   1. [Using `context.stream()`](#using-contextstream)\n   1. [Using the `_stream` parameter](#using-the-_stream-parameter)\n      1. [Selectively listening to specific streams](#selectively-listening-to-specific-streams)\n1. [Background execution for webhooks and chatbots](#background-execution-for-webhooks-and-chatbots)\n   1. [`@background` directive](#background-directive)\n   1. [Using the `_background` parameter](#using-the-_background-parameter)\n   1. [Background modes](#background-modes)\n1. [Debugging](#debugging)\n   1. [Using `context.log()` and `context.error()`](#using-contextlog-and-contexterror)\n   1. [Using the `_debug` parameter](#using-the-_debug-parameter)\n1. [Built-in Errors](#built-in-errors)\n1. [Testing](#testing)\n   1. [Quickstart for tests](#quickstart-for-tests)\n   1. [Custom installation for tests](#custom-installation-for-tests)\n   1. [Writing tests](#writing-tests)\n   1. [Running tests](#running-tests)\n1. [Deployment](#deployment)\n   1. [via `instant deploy`](#via-instant-deploy)\n   1. [Custom deployments](#custom-deployments)\n1. [More Information](#more-information)\n   1. [Logging](#logging)\n   1. [Error monitoring](#error-monitoring)\n   1. [Middleware](#middleware)\n1. [Roadmap and Feedback](#roadmap-and-feedback)\n1. [Acknowledgements](#acknowledgements)\n\n## Getting Started\n\n### Quickstart\n\nThe quickest way to get started with Instant API is via the\n`instant.dev` [command line tools](https://github.com/instant-dev/instant.dev).\nIt is the easiest way to get your Instant API project set up, generate new endpoints,\nmanage tests and comes with built-in deployment tooling for Vercel or AWS.\nIt comes packaged with the [Instant ORM](https://github.com/instant-dev/orm) which\nmakes setting up a Postgres-based backend a breeze.\n\n```shell\nnpm i instant.dev -g\ncd ~/projects\nmkdir my-new-project\ncd my-new-project\ninstant init\n```\n\nFrom there, you can use more advanced features:\n\n```shell\n# Create an endpoint (test generated automatically)\ninstant g:endpoint first_endpoint\n\n# Run your server\ninstant serve\n\n# Run tests\ninstant test\n\n# See all available commands\ninstant help\n```\n\n### Custom installation\n\n**Note:** Most of this documentation will assume you are using the\n`instant.dev` [command line tool](https://github.com/instant-dev/). It is the\nrecommended way to get your Instant API project set up, generate new endpoints,\nmanage tests and comes with built-in deployment tooling for Vercel or AWS.\n\nTo use Instant API without the command line tools, you can do the following;\n\n```shell\ncd path/to/my/project\nnpm i @instant.dev/api --save\n```\n\nThen add the following to `package.json`:\n\n```json\n  \"scripts\": {\n    \"start\": \"node instant.mjs\"\n  },\n```\n\nAnd copy the following file to `instant.mjs`:\n\n```javascript\n// Third-party imports\nimport InstantAPI from '@instant.dev/api';\n\n// Native imports\nimport cluster from 'cluster';\nimport os from 'os';\n\n// Shorthand references\nconst Daemon = InstantAPI.Daemon;\nconst Gateway = InstantAPI.Daemon.Gateway;\nconst EncryptionTools = InstantAPI.EncryptionTools;\n\n// Constants\nconst ENVIRONMENT = process.env.NODE_ENV || 'development';\nconst PORT = process.env.PORT || 8000;\n\nif (cluster.isPrimary) {\n\n  // Multi-process daemon\n  const daemon = new Daemon(\n    ENVIRONMENT !== 'development'\n      ? os.cpus().length\n      : 1\n  );\n  daemon.start(PORT);\n\n} else {\n\n  // Individual webserver startup\n  const gateway = new Gateway({debug: ENVIRONMENT !== 'production'});\n  gateway.load(process.cwd());       // load routes from filesystem\n  gateway.listen(PORT);              // start server\n\n}\n```\n\nTo start your server, simply run:\n\n```javascript\nnpm start\n```\n\n## Endpoints and Type Safety\n\nInstant API relies on a Function as a Service model for endpoint execution: every\n{Route, HTTP Method} combination is modeled as an exported function. To add parameter\nvalidation a.k.a. type safety to your endpoints, you simply document your exported\nfunctions with a slightly modified JSDoc specification comment block. For example,\nthe simplest endpoint possible would look like this;\n\nFile: `functions/index.js`\n\n```javascript\nexport default async function () {\n  return `hello world`;\n}\n```\n\nAnd you could execute it with;\n\n```shell\ncurl localhost:8000/\n\u003e \"hello world\"\n```\n\nAssuming you are running `instant serve` or `npm start` on port `8000`. See\n[Getting started](#getting-started) for more details on starting your server.\n\n### Creating endpoints\n\nThe easiest way to create endpoints for Instant API is via the `instant.dev`\n[command line tools](https://github.com/instant-dev/instant). Once your project\nhas been initialized, you can write:\n\n```shell\ninstant g:endpoint path/to/endpoint\n```\n\nAnd voila! A new blank endpoint has been created at `/functions/path/to/endpoint/index.mjs`.\n\nIf you want to create an endpoint manually, just create a new `.mjs` file in the `functions/`\ndirectory and make sure it outputs a function corresponding to at least one HTTP method:\n`GET`, `POST`, `PUT` or `DELETE`.\n\n\n### Responding to HTTP methods\n\nIn the example above, we used `export default` to export a default function.\nThis function will respond to to all `GET`, `POST`, `PUT` and `DELETE` requests\nwith the same function. Alternatively, we can export functions for each method individually,\nlike so:\n\nFile: `functions/index.js`\n\n```javascript\nexport async function GET () {\n  return `this was a GET request!`;\n}\n\nexport async function POST () {\n  return `this was a POST request!`;\n}\n```\n\nAny method not specified in this manner will automatically return an HTTP 501 error\n(Not Implemented). You can test these endpoints like so;\n\n```shell\ncurl -X GET localhost:8000/\n\u003e \"this was a GET request!\"\n\ncurl -X POST localhost:8000/\n\u003e \"this was a POST request!\"\n\ncurl -X PUT localhost:8000/\n\u003e {\"error\":...} # Returns NotImplementedError (501)\n```\n\n**Note:** Method names are **case sensitive**, they **must** be uppercase.\nInstant API will throw an error if the exports aren't read properly.\n\n#### Endpoint lifecycle\n\nWhen endpoint files, like `functions/index.js` above, are accessed they\nare imported **only once per process**. Code outside of `export` statements\nis executed lazily the first time the function is executed. By default,\nin a production server environment, Instant API will start one process per virtual core.\n\nEach exported function will be executed every time it is called. For the most part,\nyou should only use the area external to your `export` statements for critical library\nimports and frequently accessed object caching; **not for data persistence**.\n\nHere's an example using [Instant ORM](https://github.com/instant-dev/orm):\n\n```javascript\n// DO THIS: Cache connections and commonly used objects, constructors\n\n// Executed only once per process: lazily on first execution\nimport InstantORM from '@instant.dev/orm';\nconst Instant = await InstantORM.connectToPool(); // connect to postgres\nconst User = Instant.Model('User'); // access User model\n\n/**\n * Find all users matching a provided username\n * @param {string} searchQuery Username portion to search for\n * @returns {object[]} users\n */\nexport async function GET (searchQuery) {\n  // Executed each time endpoint called\n  return await User.query()\n    .where({username__icontains: searchQuery})\n    .select();\n}\n```\n\nHere's an example of what you should **not** do:\n\n```javascript\n// DO NOT DO THIS: data persistence not reliable in production workloads\n//    Could be on a multi-core server or serverless deployment e.g. Vercel\n\nlet pageViews = 0;\n\n/**\n * Track views -- poorly. Persistence unreliable!\n */\nexport async function GET () {\n  return `This page has been viewed ${++pageViews} times`;\n}\n```\n\n#### Typing your endpoint\n\nEndpoints can be typed using a slightly modified JSDoc specification that is\neasily interpreted and syntax highlighted by most modern code editors. You\ntype your endpoint by (1) providing a comment block immediately preceding\nyour exported function and / or (2) providing default values for your\nexported function.\n\n**Note:** Parameter documentation for typing is an all-or-none affair.\nInstant API will refuse to start up if documented endpoints do not match the\nfunction signature.\n\n##### Undocumented parameters\n\nBy default, if you do not document your parameters **at all**, they will\nbe assumed to be type `any` and all be required. If you provided default\nvalues, the parameters will be optional but will assume the type of their\ndefault value.\n\n```javascript\nexport async function GET (name, age = 25) {\n  return `hello ${name} you are ${age}`;\n}\n```\n\nIn this case, `name` is **required** by can take on any type. `age` is **optional**\nbut must be a `number`.\n\n```shell\ncurl -X GET localhost:8000/\n\u003e {\"error\":...} # Returns ParameterError (400) -- name is required\n\ncurl -X GET localhost:8000/?name=world\n\u003e \"hello world you are 25\"\n\ncurl -X GET 'localhost:8000/?name=world\u0026age=lol'\n\u003e {\"error\":...} # Returns ParameterError (400) -- age should be a number\n\ncurl -X GET 'localhost:8000/?name=world\u0026age=99'\n\u003e \"hello world you are 99\"\n```\n\n##### Required parameters\n\nA parameter is **required** if you **do not provide a default value** in the function\nsignature. For example;\n\n```javascript\n/**\n * @param {string} name \n */\nexport async function GET (name) {\n  return `hello ${name}`;\n}\n```\n\nWill return a `ParameterError` with status code `400` indicating the `name`\nparameter is required if no `name` is passed in to the endpoint.\n\n```shell\ncurl -X GET localhost:8000/\n\u003e {\"error\":...} # Returns ParameterError (400)\n\ncurl -X GET localhost:8000/?name=world\n\u003e \"hello world\"\n```\n\n##### Optional parameters\n\nA parameter is **optional** if you:\n\n- prefix the type with a `?`\n- AND / OR provide a default value in the function signature\n\nIf you prefix the type with `?`, the default value is assumed to be `null`.\nYou **can not** use `undefined` as an acceptable endpoint parameter value.\n\nFor example;\n\n```javascript\n/**\n * @param {?string} name \n * @param {number} age\n */\nexport async function GET (name, age = 4.2e9) {\n  return `hello ${name}, you are ${age}`;\n}\n```\n\nEven though `name` was not provided in the function signature, the `?` in\n`{?string}` indicates that this parameter is optional. It will be given a\ndefault value of `null`. When included in a template string, null will be\nprinted as the string `\"null\"`.\n\n```shell\ncurl -X GET localhost:8000/\n\u003e \"hello null, you are 4200000000\"\n\ncurl -X GET localhost:8000/?name=world\n\u003e \"hello world, you are 4200000000\"\n\ncurl -X GET 'localhost:8000/?name=world\u0026age=101'\n\u003e \"hello world, you are 101\"\n```\n\n#### `context` object\n\nThe `context` object is a \"magic\" parameter that can be appended to any\nfunction signature. It **can not** be documented and you **can not**\nuse \"context\" as a parameter name. The other magic parameters are\n`_stream` ([Streaming and LLM support](#streaming-and-llm-support)),\n`_debug` ([Debug](#debugging)) and `_background`. However, only\n`context` can be added to your function signature.\n\nYou can use `context` to access execution-specific information like so;\n\n```javascript\nexport async function GET (context) {\n  console.log(context.http.method);   // \"GET\"\n  console.log(context.http.body);     // Request body (utf8)\n  console.log(context.remoteAddress); // IP address\n  return context;                     // ... and much more\n}\n```\n\nIt also comes with a `context.stream()` function which you can read about\nin [Streaming and LLM support](#streaming-and-llm-support).\n\nA full list of available properties is as follows;\n\n```javascript\n{\n  \"name\": \"endpoint_name\",\n  \"alias\": \"request_pathname\",\n  \"path\": [\"request_pathname\", \"split\", \"by\", \"/\"],\n  \"params\": {\"jsonified\": \"params\", \"passed\": \"via_query_and_body\"},\n  \"remoteAddress\": \"ipv4_or_v6_address\",\n  \"uuid\": \"request_uuid\",\n  \"http\": {\n    \"url\": \"request_url\",\n    \"method\": \"request_method\",\n    \"headers\": {\"request\": \"headers\"},\n    \"body\": \"request_body_utf8\",\n    \"json\": \"request_body_json_if_applicable_else_null\",\n  },\n  \"stream\": function stream (name, value) { /* ... */ }\n}\n```\n\n#### API endpoints: `functions/` directory\n\nBy default, anything in the root `functions/` directory of an Instant API\nproject is exported as an API endpoint. All `.js`, `.cjs` and `.mjs` files\nare valid. We have not covered CommonJS-styled exports here as they are supported\nfor legacy purposes and not recommended for forward-facing development.\n\nRouting is handled by mapping the pathname of an HTTP request to the internal file\npathname of the function, not including `functions/` or the file extension. For example,\nan HTTP request to `/v1/hello-world` will trigger `functions/v1/hello-world.js`.\n\nThere are four \"magic\" filenames that can be used to handle indices or subdirectories.\n`index.mjs` / `__main__.mjs` will act as a handler for the root directory and\n`404.mjs` / `__notfound__.mjs` will act as a handler for any subdirectory or file\nthat is not otherwise defined.\n\n##### Index routing with `index.mjs`\n\nAlias: `__main__.mjs`\n\nHandler for the root directory. For example, \n`functions/v1/stuff/index.mjs` is accessible via `/v1/stuff`.\n\n##### Subdirectory routing with `404.mjs`\n\nAlias: `__notfound__.mjs`\n\nHandler for subdirectories not otherwise defined. For example, with the following\ndirectory structure:\n\n```yaml\n- functions/\n  - v1/\n    - stuff/\n      - 404.mjs\n      - abc.mjs\n```\n\nThe following HTTP request pathnames would map to these endpoints;\n\n- `/v1/stuff` -\u003e `functions/v1/stuff/404.mjs`\n- `/v1/stuff/abc` -\u003e `functions/v1/stuff/abc.mjs`\n- `/v1/stuff/abcd` -\u003e `functions/v1/stuff/404.mjs`\n- `/v1/stuff/abc/def` -\u003e `functions/v1/stuff/404.mjs`\n\nYou can use this behavior to define custom routing schemes. If you want\ncustom 404 error pages we recommend using [Subdirectory routing with `404.html`](#subdirectory-routing-with-404html) instead.\n\n#### Static files: `www/` directory\n\nInstant API comes with built-in static file hosting support. Instead\nof putting files in the `functions/` directory, put any file you want\nin the `www/` directory to automatically have it hosted as a standalone static\nfile.\n\nThe rules for static hosting are as follows;\n\n- The server root `/` maps directly to `www/`\n- e.g. `/image.png` -\u003e `www/image.png`\n- All `.htm` and `.html` files will be available with AND without suffixes\n- `/hello` -\u003e `www/hello.html`\n- `/hello.html` -\u003e `www/hello.html`\n- API and static routes **can not** conflict\n- `functions/wat.mjs` would overlap with `www/wat.htm` and is **not** allowed\n\nThere are four \"magic\" filenames that can be used to handle indices or subdirectories.\n`index.html` / `index.htm` will act as a handler for the root directory and\n`404.html` / `404.htm` will act as a handler for any subdirectory or file\nthat is not otherwise defined.\n\n##### Index routing with `index.html`\n\nAlias: `index.htm`\n\nSame behavior as `index.js` for API routes. Handler for the root pathname.\n\n##### Subdirectory routing with `404.html`\n\nAlias: `404.htm`\n\nSame behavior as `404.js` for API routes. Handler for subdirectories that are\nnot otherwise defined. Ideal use case is for custom 404 error pages.\n\n### Type Safety\n\nTypes are applied to parameter and schema validation based upon the comment block\npreceding your exported endpoint function.\n\n```javascript\n/**\n * My GET endpoint\n * @param {any} myparam\n */\nexport async function GET (myparam) {\n  // do something with myparam\n}\n```\n\n#### Supported types\n\n| Type | Definition | Example Parameter Input Values (JSON) |\n| ---- | ---------- | -------------- |\n| boolean | True or False | `true` or `false` |\n| string | Basic text or character strings | `\"hello\"`, `\"GOODBYE!\"` |\n| number | Any double-precision [Floating Point](https://en.wikipedia.org/wiki/IEEE_floating_point) value | `2e+100`, `1.02`, `-5` |\n| float | Alias for `number` | `2e+100`, `1.02`, `-5` |\n| integer | Subset of `number`, integers between `-2^53 + 1` and `+2^53 - 1` (inclusive) | `0`, `-5`, `2000` |\n| object | Any JSON-serializable Object | `{}`, `{\"a\":true}`, `{\"hello\":[\"world\"]}` |\n| object.http | An object representing an HTTP Response. Accepts `headers`, `body` and `statusCode` keys | `{\"body\": \"Hello World\"}`, `{\"statusCode\": 404, \"body\": \"not found\"}`, `{\"headers\": {\"Content-Type\": \"image/png\"}, \"body\": Buffer.from(...)}` |\n| array | Any JSON-serializable Array | `[]`, `[1, 2, 3]`, `[{\"a\":true}, null, 5]` |\n| buffer | Raw binary octet (byte) data representing a file. | `{\"_bytes\": [8, 255]}` or `{\"_base64\": \"d2h5IGRpZCB5b3UgcGFyc2UgdGhpcz8/\"}` |\n| any | Any value mentioned above | `5`, `\"hello\"`, `[]` |\n\n#### Type coercion\n\nThe `buffer` type will automatically be converted to a `Buffer` from any `object` with a\n**single key-value pair matching the footprints** `{\"_bytes\": []}` or `{\"_base64\": \"\"}`.\n\nOtherwise, parameters provided to a function are expected to match their\ndefined types. Requests made over HTTP GET via query parameters or POST data\nwith type `application/x-www-form-urlencoded` will be automatically\nconverted from strings to their respective expected types, when possible.\n\nOnce converted, all types will undergo a final type validation. For example, passing\na JSON `array` like `[\"one\", \"two\"]` to a parameter that expects an `object` will convert\nfrom string to JSON successfully but fail the `object` type check.\n\n| Type | Conversion Rule |\n| ---- | --------------- |\n| boolean | `\"t\"` and `\"true\"` become `true`, `\"f\"` and `\"false\"` become `false`, otherwise will be kept as string |\n| string | No conversion: already a string |\n| number | Determine float value, if NaN keep as string, otherwise convert |\n| float | Determine float value, if NaN keep as string, otherwise convert |\n| integer | Determine float value, if NaN keep as string, otherwise convert: may fail integer check |\n| object | Parse as JSON, if invalid keep as string, otherwise convert: may fail object check |\n| object.http | Parse as JSON, if invalid keep as string, otherwise convert: may fail object.http check |\n| array | Parse as JSON, if invalid keep as string, otherwise convert: may fail array check |\n| buffer | Parse as JSON, if invalid keep as string, otherwise convert: may fail buffer check |\n| any | No conversion: keep as string |\n\n#### Combining types\n\nYou can combine types using the pipe `|` operator. For example;\n\n```javascript\n/**\n * @param {string|integer} myparam String or an integer\n */\nexport async function GET (myparam) {\n  // do something\n} \n```\n\nWill accept a `string` or an `integer`. Types defined this way will validate against\nthe provided types in order of appearance. In this case, since it is a GET request and\nall parameters are passed in as strings via query parameters, `myparam` will **always**\nbe received a string because it will successfully pass the string type coercion and\nvalidation first.\n\nHowever, if you use a POST request:\n\n```javascript\n/**\n * @param {string|integer} myparam String or an integer\n */\nexport async function POST (myparam) {\n  // do something\n} \n```\n\nThen you can pass in `{\"myparam\": \"1\"}` or `{\"myparam\": 1}` via the body which would both\npass type validation.\n\nYou can combine as many types as you'd like:\n\n```javascript\n@param {string|buffer|array|integer}\n```\n\nIncluding `any` in your list will, as expected, override any other type specifications.\n\n#### Enums and restricting to specific values\n\nSimilar to combining types, you can also include specific JSON values in your type definitions:\n\n```javascript\n/**\n * @param {\"one\"|\"two\"|\"three\"|4} myparam String or an integer\n */\nexport async function GET (myparam) {\n  // do something\n} \n```\n\nThis allows you to restrict possible inputs to a list of allowed values. In the case above,\nsending `?myparam=4` via HTTP GET **will** successfully parse to  `4` (`Number`), because it\nwill fail validation against the three string options.\n\nYou can combine specific values and types in your definitions freely:\n\n```javascript\n@param {\"one\"|\"two\"|integer}\n```\n\nJust note that certain combinations will invalidate other list items. Like `{1|2|integer}` will\naccept any valid integer.\n\n#### Sizes (lengths)\n\nThe types `string`, `array` and `buffer` support sizes (lengths) via the `{a..b}` modifier on the type.\nFor example;\n\n```javascript\n@param {string{..9}}  alpha\n@param {string{2..6}} beta\n@param {string{5..}}  gamma\n```\n\nWould expect `alpha` to have a maximum length of `9`, `beta` to have a minimum length of\n`2` but a maximum length of `6`, and `gamma` to have a minimum length of `5`.\n\n#### Ranges\n\nThe types `number`, `float` and `integer` support ranges via the `{a,b}` modifier on the type.\nFor example;\n\n```javascript\n@param {number{,1.2e9}} alpha\n@param {number{-10,10}} beta\n@param {number{0.870,}} gamma\n```\n\nWould expect `alpha` to have a maximum value of `1 200 000 000`, `beta` to have a minimum value of\n`-10` but a maximum value of `10`, and `gamma` to have a minimum value of `0.87`.\n\n#### Arrays\n\nArrays are supported via the `array` type. You can optionally specify a schema for the array\nwhich applies to **every element in the array**. There are two formats for specifying array\nschemas, you can pick which works best for you:\n\n```javascript\n@param {string[]}      arrayOfStrings1\n@param {array\u003cstring\u003e} arrayOfStrings2\n```\n\nFor multi-dimensional arrays, you can use nesting:\n\n```javascript\n@param {integer[][]}           array2d\n@param {array\u003carray\u003cinteger\u003e\u003e} array2d_too\n```\n\n**Please note**: Combining types are not currently available in array schemas. Open up an\nissue and let us know if you'd like them and what your use case is! In the meantime;\n\n```javascript\n@param {integer[]|string[]}\n```\n\nWould successfully define an array of integers or an array of strings.\n\n#### Object schemas\n\nTo define object schemas, use the subsequent lines of the schema after your initial object\ndefinition to define individual properties. For example, the object\n`{\"a\": 1, \"b\": \"two\", \"c\": {\"d\": true, \"e\": []}` Could be defined like so:\n\n```javascript\n@param {object}  myObject\n@param {integer} myObject.a\n@param {string}  myObject.b\n@param {object}  myObject.c\n@param {boolean} myObject.c.d\n@param {array}   myObject.c.e\n```\n\nTo define object schemas that are members of arrays, you must identify the array component in the\nproperty name with `[]`. For example:\n\n```javascript\n@param {object[]} topLevelArray\n@param {integer}  topLevelArray[].value\n@param {object}   myObject\n@param {object[]} myObject.subArray\n@param {string}   myObject.subArray[].name\n```\n\n### Parameter validation\n\nParameter validation occurs based on types as defined per [Type Safety](#type-safety).\nThe process for parameter validation takes the following steps:\n\n1. Read parameters from the HTTP query string as type `application/x-www-form-urlencoded`\n1. If applicable, read parameters from the HTTP body based on the request `Content-Type`\n   - Supported content types:\n     - `application/json`\n     - `application/x-www-formurlencoded`\n     - `multipart/form-data`\n     - `application/xml`, `application/atom+xml`, `text/xml`\n1. Query parameters **can not** conflict with body parameters, throw an error if they do\n1. Perform type coercion on `application/x-www-form-urlencoded` inputs (query and body, if applicable)\n1. Validate parameters against their expected types, throw an error if they do not match\n\nDuring this process, you can encounter a `ParameterParseError` or a `ParameterError` both with\nstatus code `400`. `ParameterParseError` means your parameters could not be parsed based on\nthe expected or provided content type, and `ParameterError` is a validation error against the\nschema for your endpoint.\n\n#### Query and Body parsing with `application/x-www-form-urlencoded`\n\nMany different standards have been implemented and adopted over the years for\nHTTP query parameters and how they can be used to specify objects and arrays.\nTo make things easy, Instant API supports all common query parameter parsing formats.\n\nHere are some query parameter examples of parsing form-urlencoded data:\n\n- Arrays\n  - Duplicates: `?arr=1\u0026arr=2` becomes `[1, 2]`\n  - Array syntax: `?arr[]=1\u0026arr[]=2` becomes `[1, 2]`\n  - Index syntax: `?arr[0]=1\u0026arr[2]=3` becomes `[1, null, 3]`\n  - JSON syntax: `?arr=[1,2]` becomes `[1, 2]`\n- Objects\n  - Bracket syntax: `?obj[a]=1\u0026obj[b]=2` becomes `{\"a\": 1, \"b\": 2}`\n  - Dot syntax: `?obj.a=1\u0026obj.b=2` becomes `{\"a\": 1, \"b\": 2}`\n    - Nesting: `?obj.a.b.c.d=t` becomes `{\"a\": {\"b\": {\"c\": {\"d\": true}}}}`\n  - JSON syntax: `?obj={\"a\":1,\"b\":2}` becomes `{\"a\": 1, \"b\": 2}`\n\n#### Query vs. Body parameters\n\nWith Instant API, **query and body parameters can be used interchangeably**.\nThe general expectation is that `POST` and `PUT` endpoints should typically\nonly interface with the content body, but API consumers should be able to freely\nmanipulate query parameters if they want to play around.\nFor example, the endpoint defined by:\n\n```javascript\n/**\n * Hello world endpoint\n * @param {string} name\n * @param {number} age\n */\nexport async function POST (name, age) {\n  return `hello ${name}, you are ${age}!`;\n}\n```\n\nCould be triggered successfull via;\n\n```shell\ncurl -X POST 'localhost:8000/hello-world?name=world\u0026age=99'\ncurl -X POST 'localhost:8000/hello-world?name=world' --data '{\"age\":99}'\ncurl -X POST 'localhost:8000/hello-world' --data '{\"name\":\"world\",\"age\":99}'\n```\n\nGenerally speaking, our motivation for this pattern comes from two observations;\n\n1. In decades of software development we have never seen a legitimate use case for\n   query parameters and body parameters with the same name on a single endpoint\n2. Exposing APIs this way is a lot easier for end users to play with\n\nTo prevent unexpected errors, naming collisions will throw an error at the gateway layer,\nbefore your endpoint is executed.\n\n### CORS (Cross-Origin Resource Sharing)\n\nBy default, all endpoints have a **completely open** CORS policy, they all return\nthe header `Access-Control-Allow-Origin: *`.\n\nTo restrict endpoints to specific URLs use the `@origin` directive. You can add\nas many of these as you'd like.\n\n```javascript\n/**\n * My CORS-restricted endpoint\n * @origin staging.my-website.com\n * @origin http://localhost:8000\n * @origin https://my-website.com\n * @origin =process.env.ALLOWED_ORIGIN\n * @origin =process.env.ANOTHER_ALLOWED_ORIGIN\n * @param {number} age\n */\nexport async function POST (name, age) {\n  return `hello ${name}, you are ${age}!`;\n}\n```\n\nThe CORS `Access-Control-Allow-Origin` policy will be set like so;\n\n- If no protocol is specified, allow all traffic from the URL\n- If port is specified, only allow traffic from the URL on the specified port\n- If `http://` is specified, only allow `http` protocol traffic from the URL\n- If `https://` is specified, only allow `https` protocol traffic from the URL\n- If origin starts with `=process.env.`, it will rely on the specified environment variable\n\nNote that `=process.env.ENV_NAME` entries will be loaded at startup time. Dynamically\nchanging `process.env` afterwards will have no effect on your allowed origins.\n\n### Returning responses\n\nReturning API responses from your endpoint is easy. Just add a `return` statement\nwith whatever data you would like to return.\n\n```javascript\nexport async function GET () {\n  return `hello world`; // works as expected\n}\n```\n\nBy default, all responses will be `JSON.stringify()`-ed and returned with the\n`Content-Type` header set to `application/json`.\n\n```shell\ncurl localhost:8000/hello-world\n\u003e \"hello world\"\n```\n\nThere are two exceptions: returning an `object.http` object\n(containing `statusCode`, `headers`, and `body`) allows you to provide a\n[Custom HTTP response](#custom-http-responses) and returning a `Buffer`, which are\ntreated as [raw binary (file) data](#returning-files-with-buffer-responses).\n\n#### `@returns` type safety\n\nSimilar to [Parameter validation](#parameter-validation), you can enforce a type\nschema on the return value of your endpoint like so;\n\n```javascript\n/**\n * @returns {object} message\n * @returns {string} message.content\n */\nexport async function GET () {\n  return {message: `hello world`};\n}\n```\n\nThe difference between `@returns` type safety as compared to `@param` validation\nis that this type safety mechanism is run **after** your code has been executed.\nIf you fail a `@returns` type safety check, the user receives a `ValueError` with\nstatus code `502`: a server error. The function may have executed successfully\nbut the value does not fulfill the promised API contract. This functionality exists\nto ensure users can trust the type contract of your API. To avoid production snafus,\nwe recommend [writing tests](#writing-tests) to validate that your endpoints\nreturn the values you expect them to.\n\n#### Error responses\n\nAny uncaught promises or thrown errors will result in a `RuntimeError` with a status\ncode of `420` (unknown) by default. To customize error codes, check out\n[Throwing Errors](#throwing-errors).\n\n#### Custom HTTP responses\n\nTo return a custom HTTP response, simply return an object with one or all of the following\nkeys: `statusCode` (integer), `headers` (object) and `body` (Buffer). You can specify\nthis in the `@returns` schema, however Instant API will automatically detect the type.\n\n```javascript\n/**\n * I'm a teapot\n */\nexport async function GET () {\n  return {\n    statusCode: 418,\n    headers: {'Content-Type', 'text/plain'},\n    body: Buffer.from(`I'm a teapot!`)\n  };\n}\n```\n\n#### Returning files with Buffer responses\n\nIf you would like to return a raw file from the file system, compose binary data into a\ndownloadable file, or dynamically generate an image (e.g. with Dall-E or Stable Diffusion)\nyou can build a custom HTTP response as per above - but Instant API makes it a little easier\nthan that.\n\nIf you return a `Buffer` object you can optionally specify a `contentType` to set the\n`Content-Type` http header like so:\n\n```javascript\nimport fs from 'fs';\n\n/**\n * Return an image from the filesystem to be displayed\n */\nexport async function GET () {\n  const buffer = fs.readFileSync('./path/to/image.png');\n  buffer.contentType = 'image/png';\n  return buffer;\n}\n```\n\n#### Streaming responses\n\nInstant API has first-class support for streaming using the `text/event-stream`\ncontent type and the \"magic\" `_stream` parameter.\nYou can read more in [Streaming and LLM support](#streaming-and-llm-support).\n\n#### Debug responses\n\nIn `development` environments, e.g. when `process.env.NODE_ENV=development`, you\ncan stream the results of any function using the \"magic\" `_debug` parameter.\nThis allows you to monitor function execution in the browser for long-running jobs.\nYou can read more in [Debugging](#debugging).\n\n### Throwing Errors\n\nWhenever a `throw` statement is executed or a Promise is uncaught within the context\nof an Instant API endpoint, the default behavior is to return a `RuntimeError` with\na status code of `420`: your browser will refer to this as \"unknown\", we think of it\nas \"confused\".\n\nTo specify a specific error code between 400 and 404, simply throw an error prefixed\nwith the code and a colon like so:\n\n```javascript\n/**\n * Errors out\n */\nexport async function GET () {\n  throw new Error(`400: No good!`);\n}\n```\n\nWhen you execute this function you would see a `BadRequestError` with a status code of `400`:\n\n```json\n{\n  \"error\": {\n    \"type\": \"BadRequestError\",\n    \"message\": \"No good!\"\n  }\n}\n```\n\nThe following error codes will automatically map to error types:\n\n- 400: `BadRequestError`\n- 401: `UnauthorizedError`\n- 402: `PaymentRequiredError`\n- 403: `ForbiddenError`\n- 404: `NotFoundError`\n\n## OpenAPI Specification Generation\n\nOpenAPI specifications are extremely helpful for machine-readability but are\nextremely verbose. Instant API allows you to manage all your type signatures\nand parameter validation via JSDoc in a very terse manner while automatically\ngenerating your OpenAPI specification for you. By default Instant API will\ncreate three schema files based on your API:\n\n- `localhost:8000/.well-known/openapi.json`\n- `localhost:8000/.well-known/openapi.yaml`\n- `localhost:8000/.well-known/schema.json`\n\nThe first two are [OpenAPI schemas](https://www.openapis.org/) and the\nfinal one is a [JSON schema](https://json-schema.org/) which outputs a\n`{\"functions\": [...]}` object. The latter is primarily intended for use\nwith [OpenAI function calling](https://openai.com/blog/function-calling-and-other-api-updates)\nand other LLM integrations.\n\n### OpenAPI Output Example\n\nAs a simple example, consider the following endpoint:\n\n```javascript\n/**\n * Gets a \"Hello World\" message\n * @param {string} name\n * @param {number{12,199}} age\n * @returns {string} message\n */\nexport async function GET (name, age) {\n  return `hello ${name}, you are ${age} and you rock!`\n}\n\n/**\n * Creates a new hello world message\n * @param {object} body\n * @param {string} body.content\n * @returns {object}  result\n * @returns {boolean} result.created\n */\nexport async function POST (body) {\n  console.log(`Create body ... `, body);\n  return {created: true};\n}\n```\n\nOnce saved as part of your project, if you open `localhost:8000/.well-known/openapi.yaml`\nin your browser, you should receive the following OpenAPI specification:\n\n```yaml\nopenapi: \"3.1.0\"\ninfo:\n  version: \"development\"\n  title: \"(No name provided)\"\n  description: \"(No description provided)\"\nservers:\n  - url: \"localhost\"\n    description: \"Instant API Gateway\"\npaths:\n  /hello-world/:\n    get:\n      summary: \"Gets a \\\"Hello World\\\" message\"\n      description: \"Gets a \\\"Hello World\\\" message\"\n      operationId: \"service_localhost_hello_world_get\"\n      parameters:\n        - in: \"query\"\n          name: \"name\"\n          schema:\n            type: \"string\"\n        - in: \"query\"\n          name: \"age\"\n          schema:\n            type: \"number\"\n            minimum: 12\n            maximum: 199\n      responses:\n        200:\n          content:\n            application/json:\n              schema:\n                type: \"string\"\n    post:\n      summary: \"Creates a new hello world message\"\n      description: \"Creates a new hello world message\"\n      operationId: \"service_localhost_hello_world_post\"\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: \"object\"\n              properties:\n                body:\n                  type: \"object\"\n                  properties:\n                    content:\n                      type: \"string\"\n                  required:\n                    - \"content\"\n              required:\n                - \"body\"\n      responses:\n        200:\n          content:\n            application/json:\n              schema:\n                type: \"object\"\n                properties:\n                  created:\n                    type: \"boolean\"\n                required:\n                  - \"created\"\n```\n\n### JSON Schema Output Example\n\nUsing the same endpoint defined above would produce the following JSON schema\nat `localhost:8000/.well-known/schema.json`:\n\n```json\n{\n  \"functions\": [\n    {\n      \"name\": \"hello-world_get\",\n      \"description\": \"Gets a \\\"Hello World\\\" message\",\n      \"route\": \"/hello-world/\",\n      \"url\": \"localhost/hello-world/\",\n      \"method\": \"GET\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"name\": {\n            \"type\": \"string\"\n          },\n          \"age\": {\n            \"type\": \"number\",\n            \"minimum\": 12,\n            \"maximum\": 199\n          }\n        },\n        \"required\": [\n          \"name\",\n          \"age\"\n        ]\n      }\n    },\n    {\n      \"name\": \"hello-world\",\n      \"description\": \"Creates a new hello world message\",\n      \"route\": \"/hello-world/\",\n      \"url\": \"localhost/hello-world/\",\n      \"method\": \"POST\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"body\": {\n            \"type\": \"object\",\n            \"properties\": {\n              \"content\": {\n                \"type\": \"string\"\n              }\n            },\n            \"required\": [\n              \"content\"\n            ]\n          }\n        },\n        \"required\": [\n          \"body\"\n        ]\n      }\n    }\n  ]\n}\n```\n\n### Hiding endpoints with `@private`\n\nDon't want all of your endpoints exposed to your end users? No problem.\nSimply mark an endpoint as `@private`, like so:\n\n```javascript\n/**\n * My admin function\n * @private\n */\nexport async function POST (context) {\n  await authenticateAdminUser(context);\n  doSomethingAdministrative();\n  return `ok!`;\n}\n```\n\nThis will prevent it from being shown in either your OpenAPI or JSON schema\noutputs.\n\n## Streaming and LLM support\n\nInstant API comes with built-in support for streaming\n[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)\nwith the `text/event-stream` content type. This allows you to send\nevents to a user as they are received, and is ideal for developing\nLLM-based APIs.\n\nStreams are typed events that can be sent via a special `context.stream()`\nmethod. They must be defined using the `@stream` directive in the JSDoc\ndescriptor for your API endpoint. Streams are typed like `@param` and `@returns`,\nand these types are enforced: if you choose to `context.stream(name, payload)` the\nwrong data format in `payload`, your function will throw an error.\n\nBy default, functions **will not stream** even when a `@stream` is defined\nand `context.stream()` is called. They will simply accept parameters and output a\nreturned response. In order to activate streaming, you must pass a `_stream` parameter\nin the HTTP query parameters or body content - this will initiate a Server-Sent Event\nwith content type `text/event-stream`.\n\nAn example of a simple custom streaming LLM agent endpoint built with Instant API\nis below:\n\n```javascript\nimport OpenAI from 'openai';\nconst openai = new OpenAI(process.env.OPENAI_API_KEY);\n\n/**\n * Streams results for our lovable assistant\n * @param {string} query The question for our assistant\n * @stream {object}   chunk\n * @stream {string}   chunk.id\n * @stream {string}   chunk.object\n * @stream {integer}  chunk.created\n * @stream {string}   chunk.model\n * @stream {object[]} chunk.choices\n * @stream {integer}  chunk.choices[].index\n * @stream {object}   chunk.choices[].delta\n * @stream {?string}  chunk.choices[].delta.role\n * @stream {?string}  chunk.choices[].delta.content\n * @returns {object} message\n * @returns {string} message.content\n */\nexport async function GET (query, context) {\n  const completion = await openai.chat.completions.create({\n    messages: [\n      {role: `system`, content: `You are a lovable, cute assistant that uses too many emojis.`},\n      {role: `user`, content: query}\n    ],\n    model: `gpt-3.5-turbo`,\n    stream: true\n  });\n  const messages = [];\n  for await (const chunk of completion) {\n    // Stream our response as text/event-stream when ?_stream parameter added\n    context.stream('chunk', chunk); // chunk has the schema provided above\n    messages.push(chunk?.choices?.[0]?.delta?.content || '');\n  }\n  return {content: messages.join('')};\n};\n```\n\n### `@stream` type safety\n\nAll `@stream` definitions follow the same [Type Safety](#type-safety) rules\nas `@param` and `@returns` directives.\n\n### Using `context.stream()`\n\nTo send a streaming response to the client, use `context.stream(name, payload)`\nwhere `name` is the name of the stream and `payload` adheres to the correct\ntype definition for the stream.\n\n**Note:** You must import `context` properly by adding it as the final parameter\nin your function arguments.\n\n### Using the `_stream` parameter\n\nBy default, **API endpoints will not stream responses**. You must activate a\nServer-Sent event by sending the `_stream` parameter. Provided the endpoint defined\nabove, here is what you would receive given different URL accession patterns:\n\n```\nlocalhost:8000/assistant?query=how%20are%20you%20today?\n```\n\n```json\n{\n  \"content\": \"Hey there! 💁‍♀️ I'm doing great, thank you! 💖✨ How about you? 😊🌈\"\n}\n```\n\nAppending `_stream` to your query parameters gives you a live stream between a\n`@begin` and `@response` event, the latter returning an `object.http` payload\ncontaining the typical expected response for the event:\n\n```\nlocalhost:8000/assistant?query=how%20are%20you%20today?\u0026_stream\n```\n\n```shell\nid: 2023-10-25T04:29:59.115000000Z/2e7c7860-4a66-4824-98fa-a7cf71946f19\nevent: @begin\ndata: \"2023-10-25T04:29:59.115Z\"\n\n[... more events ...]\n\nevent: chunk\ndata: {\"id\":\"chatcmpl-8DPoluIgN4TDIuE1usFOKTLPiIUbQ\",\"object\":\"chat.completion.chunk\",\"created\":1698208199,\"model\":\"gpt-3.5-turbo-0613\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" 💯\"},\"finish_reason\":null}]}\n\n[... more events ...]\n\nevent: @response\ndata: {\"statusCode\":200,\"headers\":{\"X-Execution-Uuid\":\"2e7c7860-4a66-4824-98fa-a7cf71946f19\",\"X-Instant-Api\":\"true\",\"Access-Control-Allow-Origin\":\"*\",\"Access-Control-Allow-Methods\":\"GET, POST, OPTIONS, HEAD, PUT, DELETE\",\"Access-Control-Allow-Headers\":\"\",\"Access-Control-Expose-Headers\":\"x-execution-uuid, x-instant-api, access-control-allow-origin, access-control-allow-methods, access-control-allow-headers, x-execution-uuid\",\"Content-Type\":\"application/json\"},\"body\":\"{\\\"content\\\":\\\"Hey there! 🌞 I'm feeling 💯 today! Full of energy and ready to help you out. How about you? How are you doing? 🌈😊\\\"}\"}\n```\n\n#### Selectively listening to specific streams\n\nIf you do not want to listen to all streams, you can send in the `_stream` parameter as an object\nwith keys-value pairs corresponding to the streams you want to listen for. Anything with a truthy value\nwill be listened to, and `*` means \"all streams\".\n\ne.g. `localhost:8000/assistant/?_stream={\"chunk\":true}` would only listen for a `chunk` stream.\n\n## Background execution for webhooks and chatbots\n\nWhen you are developing against external webhooks, especially chatbots,\nsometimes it is important to immediately return a `200 OK` response to a third\nparty server to acknowledge receipt of the message. Instant API comes\npackaged with a \"magic\" `_background` parameter which can enable background\nexecution on specified endpoints: the endpoint will return `200 OK` instantly\nand continue processing as normal behind the scenes.\n\n### `@background` directive\n\nTo enable background processing for an endpoint, simply add a `@background` directive\nto the endpoint's JSDoc comment block:\n\n```javascript\n/**\n * @background\n */\nexport async function GET (context) {\n\n  const params = context.params; // Get all JSON params sent to endpoint\n\n  // pseudocode\n  await doSomethingThatTakesAwhile();\n  await sendDiscordMessage();\n  return {\"complete\": true};\n\n}\n```\n\n### Using the `_background` parameter\n\nBackground-enabled functions won't execute as background functions by default, you have to\nsupply them with the `_background` query or body parameter.\n\nIf you execute the above endpoint normally, like `localhost:8000/my-webhook`, you would get;\n\n```json\n{\"complete\": true}\n```\n\nHowever, if you execute using `localhost:8000/my-webhook?_background` you see:\n\n```\ninitiated \"my-webhook#GET\" ...\n```\n\nThat means it's working as expected.\n\n### Background modes\n\nMultiple modes are supported for the `@background` directive: `info`, `empty` and `params`.\n\n- `@background info` is the default and equivalent to `@background`, it will always\n  return `initiated function_name ...`\n- `@background empty` returns a completely empty body\n- `@background params` returns all parameters provided to the function\n  - You can choose to return a subset of params by including them in a space-separated list\n  - e.g. `@background params name created_at` would only reflect back `{\"name\": \"...\", \"created_at\": \"...\"}`\n    if provided\n\n## Debugging\n\nSometimes, and especially when dealing with LLMs, you find you have long-running\nendpoints that can be a pain to debug. Instant API exposes two methods, `context.log()`\nand `context.error()` that can be streamed to browser output as a Server-Sent Event\n**even if streaming is not enabled on your endpoint**. Debugging is only available\nwhen `NODE_ENV=development`.\n\nSimply append `_debug` to your query parameters (or add it to your request body)\nto see `context.log()` and `context.error()` output in real-time.\n\n### Using `context.log()` and `context.error()`\n\nThese methods are effectively wrappers for `context.stream('@stdout', payload)` and\n`context.stream('@stderr', payload)` - only they don't require streams to be enabled\nto use. They'll output as streams with the event types `@stdout` and `@stderr`, respectively,\nwhen the `_debug` parameter is passed in.\n\n### Using the `_debug` parameter\n\nGiven the following endpoint:\n\n```javascript\nconst sleep = t =\u003e new Promise(res =\u003e setTimeout(() =\u003e res(), t));\n\nexport async function GET (context) {\n  context.log(`Started!`);\n  await sleep(100);\n  context.error(`Oh no.`);\n  await sleep(500);\n  context.log(`OK!`);\n  return {complete: true};\n}\n```\n\nCalling it normally would result in:\n\n```\nlocalhost:8000/test-debug\n```\n\n```json\n{\n  \"complete\": true\n}\n```\n\nBut appending `_debug` in a `development` environment...\n\n```\nlocalhost:8000/test-debug?_debug\n```\n\n```shell\nid: 2023-10-26T00:16:42.732000000Z/cae5a3c8-14df-4222-b762-fa3f16645fe7\nevent: @begin\ndata: \"2023-10-26T00:16:42.732Z\"\n\nid: 2023-10-26T00:16:42.732000001Z/cae5a3c8-14df-4222-b762-fa3f16645fe7\nevent: @stdout\ndata: \"Started!\"\n\nid: 2023-10-26T00:16:42.834000000Z/cae5a3c8-14df-4222-b762-fa3f16645fe7\nevent: @stderr\ndata: \"Oh no.\"\n\nid: 2023-10-26T00:16:43.337000000Z/cae5a3c8-14df-4222-b762-fa3f16645fe7\nevent: @stdout\ndata: \"OK!\"\n\nevent: @response\ndata: {\"statusCode\":200,\"headers\":{\"X-Execution-Uuid\":\"cae5a3c8-14df-4222-b762-fa3f16645fe7\",\"X-Debug\":true,\"X-Instant-Api\":\"true\",\"Access-Control-Allow-Origin\":\"*\",\"Access-Control-Allow-Methods\":\"GET, POST, OPTIONS, HEAD, PUT, DELETE\",\"Access-Control-Allow-Headers\":\"\",\"Access-Control-Expose-Headers\":\"x-execution-uuid, x-debug, x-instant-api, access-control-allow-origin, access-control-allow-methods, access-control-allow-headers, x-execution-uuid\",\"Content-Type\":\"application/json\"},\"body\":\"{\\\"complete\\\":true}\"}\n```\n\n## Built-in Errors\n\nAs you develop with Instant API, you may encounter various error types as you test\nthe limits and boundariess of the framework. They are typically in the format:\n\n```json\n{\n  \"error\": {\n    \"type\": \"NamedError\",\n    \"message\": \"error message\",\n    \"stack\": \"\",\n    \"details\": {}\n  }\n}\n```\n\nNote that `error.stack` will **not** appear when `NODE_ENV=production`.\nThe `details` object may or may not be present. A list of errors you may encounter\nare below:\n\n| Error Type | Description | Details Object |\n| ---------- | ----------- | -------------- |\n| WellKnownError | Error loading a schema from `/.well-known/` pathname | N/A |\n| ClientError | Generic 4xx, usually 400 (Bad Request) | N/A |\n| ServerError | Generic 5xx, usually 500 (Internal Server Error) | N/A |\n| BadRequestError | Returned when `throw new Error('400: [...]')` is called | N/A |\n| UnauthorizedError | Returned when `throw new Error('401: [...]')` is called | N/A |\n| PaymentRequiredError | Returned when `throw new Error('402: [...]')` is called | N/A |\n| ForbiddenError | Returned when `throw new Error('403: [...]')` is called | N/A |\n| NotFoundError | Returned when `throw new Error('404: [...]')` is called | N/A |\n| ParameterParseError | Could not parse parameters based on content-type | N/A |\n| ParameterError | `@param` validation failed | `{[field]: {message: 'string', invalid: true, mismatch: 'obj.a.b', expected: {type}, actual: {value, type}}}` |\n| ValueError | `@returns` validation failed | Same as `ParameterError`, only with `\"returns\"` as the field |\n| StreamParameterError | `@stream` validation failed | Same as `ParameterError`, only with the stream name as the field |\n| StreamError | Stream specified with `context.stream()` does not exist | N/A |\n| StreamListenerError | Specific stream `name` via `_stream: {name: true}` does not exist | N/A |\n| OriginError | Invalid `@origin` specified | N/A |\n| DebugError | Could not debug endpoint with `_debug`, usually permission issue | N/A |\n| ExecutionModeError | Could not execute with `_stream` or `_background` because they are not enabled | N/A |\n| TimeoutError | Endpoint took too long to execute. Configurable on gateway initialization. | N/A |\n| FatalError | The gateway had a fatal crash event, status code `500` | N/A | \n| RuntimeError | An error was thrown during endpoint execution, status code `420` | N/A |\n| InvalidResponseHeaderError | A header specified in an `object.http` return statement was invalid | Object containing invalid headers |\n| AccessSourceError | Placeholder: Currently unused, intended for platform gateways. | N/A |\n| AccessPermissionError | Placeholder: Currently unused, intended for platform gateways. | N/A |\n| AccessAuthError | Placeholder: Currently unused, intended for platform gateways. | N/A |\n| AccessSuspendedError | Placeholder: Currently unused, intended for platform gateways. | N/A |\n| OwnerSuspendedError | Placeholder: Currently unused, intended for platform gateways. | N/A |\n| OwnerPaymentRequiredError | Placeholder: Currently unused, intended for platform gateways. | N/A |\n| RateLimitError | Placeholder: Currently unused, intended for platform gateways. | N/A |\n| AuthRateLimitError | Placeholder: Currently unused, intended for platform gateways. | N/A |\n| UnauthRateLimitError | Placeholder: Currently unused, intended for platform gateways. | N/A |\n| SaveError | Placeholder: Currently unused, intended for platform gateways. | N/A |\n| MaintenanceError | Placeholder: Currently unused, intended for platform gateways. | N/A |\n| UpdateError | Placeholder: Currently unused, intended for platform gateways. | N/A |\n| AutoformatError | Placeholder: Currently unused, intended error if formatting on static resources goes awry. | N/A |\n\n## Testing\n\nInstant API comes packaged with a `TestEngine` class for automated testing. It designed\nto work best with [mocha](https://mochajs.org/) and [chai](https://www.chaijs.com/) but\ncan be used with any runtime.\n\n### Quickstart for tests\n\nThe best way to get started with tests is via the `instant.dev`\n[command line tools](https://github.com/instant-dev/instant). Once your project is initialized,\nyou can generate tests for your endpoints with:\n\n```shell\ninstant g:test --function path/to/index.mjs\n```\n\nWhere `./functions/path/to/index.mjs` is an endpoint. This will automatically create\na test for each exported function (HTTP method) on the endpoint in:\n`./test/tests/functions/path/to/index.mjs`.\n\nAdditionally, if you are using [Instant ORM](https://github.com/instant-dev/orm), you can\ngenerate tests for individual models using:\n\n```shell\ninstant g:test --model users\n```\n\nWhich will create a test in `./test/tests/models/user.js`.\n\nFinally, To create blank (empty) tests use:\n\n```shell\ninstant g:test path/to/my_test_name\n```\n\nWhich will create an empty test in `./test/tests/path/to/my_test_name.mjs`.\n\nTests can be run via:\n\n```shell\ninstant test\n```\n\n### Custom installation for tests\n\nIf you want to write your own tests without using the `instant.dev`\n[command line tools](https://github.com/instant-dev/instant), you should\nfirst install `mocha` and `chai`.\n\n```shell\nnpm i mocha --save-dev\nnpm i chai --save-dev\n```\n\nNext, create the file `./test/run.mjs` with the following contents:\n\n```javascript\nimport InstantAPI from '@instant.dev/api';\n\nconst Gateway = InstantAPI.Gateway;\nconst TestEngine = InstantAPI.TestEngine;\nconst PORT = 7357; // Leetspeak for \"TEST\"; can be anything\n\n// Load environment variables; make sure NODE_ENV is \"test\"\nprocess.env.NODE_ENV = `test`;\n\n// Initialize and load tests; set PORT for request mocking\nconst testEngine = new TestEngine(PORT);\nawait testEngine.initialize('./test/tests');\n\n// Setup; create objects and infrastructure for tests\n// Arguments returned here will be sent to .finish()\ntestEngine.setup(async () =\u003e {\n\n  console.log();\n  console.log(`# Starting test gateway on localhost:${PORT} ... `);\n  console.log();\n\n  // Start Gateway; {debug: true} will print logs\n  const gateway = new Gateway({debug: false});\n  gateway.load(process.cwd());       // load routes from filesystem\n  gateway.listen(PORT);              // start server\n\n  return { gateway };\n\n});\n\n// Run tests; use first argument to specify a test\nconst args = process.argv.slice(3);\nif (args[0]) {\n  await testEngine.run(args[0]);\n} else {\n  await testEngine.runAll();\n}\n\n// Finish; close Gateway and disconnect from database\n// Receive arguments from .setup()\ntestEngine.finish(async ({ gateway }) =\u003e {\n  gateway.close();\n});\n```\n\nFinally, you can then add the following to `package.json`:\n\n```json\n  \"scripts\": {\n    \"test\": \"mocha test/run.mjs\"\n  }\n```\n\nTests can then be run via:\n\n```shell\nnpm test\n```\n\n### Writing tests\n\nAll tests should be put in the `./test/tests` directory. This directory\nwill be used by `TestEngine` to load all of your tests. Tests are imported\n**when they are run**, not when they are first initialized in `TestEngine`.\n\nThe structure of a test should look like this;\n\n```javascript\n// Imports, setup to begin with\nimport chai from 'chai';\nconst expect = chai.expect;\n\n// Optional: Name your test file\nexport const name = `Some tests`;\n\n/**\n * Your tests\n * @param {any} setupResult Result of the function passed to `.setup()` in `test/run.mjs`\n */\nexport default async function (setupResult) {\n\n  before(async () =\u003e {\n    // any necessary setup\n  });\n\n  it('Should test for truthiness of \"true\"', async () =\u003e {\n\n    expect(true).to.equal(true);\n\n  });\n\n  after(async () =\u003e {\n    // any necessary teardown\n  });\n\n};\n```\n\n### Running tests\n\nTests are run top-down, depth-first, alphabetically.\nGiven the following directory structure:\n\n```yaml\n- test/\n  - tests/\n    - a/\n      - second.mjs\n      - a/third.mjs\n      - b/fourth.mjs\n    - b/\n      - fifth.mjs\n    - first.mjs\n\n```\n\n- All tests in the root `test/tests` directory are executed first, alphabetically\n- All directories in `test/tests` are organized alphabetically\n- All tests in `test/tests/a/` would then run\n- Then tests in `test/tests/a/a/`\n- Then tests in `test/tests/a/b/`\n- then tests in `test/tests/b/`\n\nGiving a test order of:\n\n```yaml\n- test/tests/first.mjs\n- test/tests/a/second.mjs\n- test/tests/a/a/third.mjs\n- test/tests/a/b/fourth.mjs\n- test/tests/b/fifth.mjs\n```\n\nIf you set up via `instant.dev` [command line tools](https://github.com/instant-dev/instant),\njust run:\n\n```shell\ninstant test\n```\n\nIf you are running a custom installation, use:\n\n```shell\nnpm test\n```\n\n## Deployment\n\nInstant API can be deployed out-of-the-box to any host that respects the\n`[package.json].scripts.start` field. This includes AWS Elastic Beanstalk and Heroku.\nVercel requires a little bit of finagling. However, the `instant.dev`\n[command line tools](https://github.com/instant-dev/instant) will automatically\nmanage deployments to both AWS Elastic Beanstalk and Vercel for you.\n\n### via `instant deploy`\n\nSimple;\n\n```shell\ninstant deploy:config # follow instructions; choose Vercel or AWS\ninstant deploy --env staging # for Elastic Beanstalk\ninstant deploy --env preview # for Vercel\ninstant deploy --env production # Works for either\n```\n\n### Custom deployments\n\nWe defer to platform-specific Node.js deployment instructions. If you would\nlike to contribute some helpful tips, please submit a PR on this README!\n\n## More Information\n\n### Gateway Configuration\n\nYou can configure some custom options in your `instant.mjs` startup script.\n\n```javascript\nimport InstantAPI from '@instant.dev/api';\n\n// ... other code ...\n\nconst Gateway = InstantAPI.Daemon.Gateway;\n\nconst gateway = new Gateway({\n  port: 8000, //Defaults to 8170\n  name: 'My API Server', // Defaults to \"InstantAPI.Gateway\"\n  maxRequestSizeMB: 16, // Requests above this size will error. Defaults to 128MB.\n  defaultTimeout: 10000 // Max execution time in ms. Defaults to 600000 (10 mins).\n  debug: true // Whether to show logs or not, defaults to false\n});\n```\n\n### Logging\n\nCurrently logging is controlled entirely via the `debug` parameter in\n`new Gateway()` instatiation. When set to `true` this can very quickly overwhelm\nlog storage on high-volume API servers. We're interested in getting feedback\non your favorite log draining solutions and if there are elegant ways to integrate.\nSubmit an issue to talk to us about it!\n\n### Error monitoring\n\nThe `Gateway` instant comes with a built-in `setErrorHandler()` method to\ncapture internal errors. You can add it to `instant.mjs` or your startup script easily.\nHere's an example using [Sentry](https://sentry.io):\n\n```javascript\nimport InstantAPI from '@instant.dev/api';\nimport * as Sentry from '@sentry/node';\n\nSentry.init({\n  dsn: '__DSN__',\n  // ...\n});\n\n// ... other code ...\n\nconst Gateway = InstantAPI.Daemon.Gateway;\n\nconst gateway = new Gateway({port: process.env.PORT});\ngateway.setErrorHandler(e =\u003e Sentry.captureException(e));\ngateway.load(process.cwd());\ngateway.listen();\n```\n\nThis will catch all internal errors with the exception of;\n\n- Validation errors:\n  - ExecutionModeError, ParameterParseError, ParameterError, StreamError, StreamParameterError\n- Intentionally thrown client errors:\n  - BadRequestError, UnauthorizedError, PaymentRequiredError, ForbiddenError, NotFoundError\n\n### Middleware\n\nFor the most part, Instant API has pretty comprehensive body parsing and parameter\nvalidation libraries built right in. However, you still might want to write or use\ncustome middleware for repeated tasks; like authenticating users based on `headers`.\n\nWe recommend a simple approach of a `middleware/` directory with files that each output\na function that takes `context` as a single argument:\n\ne.g. in `./middleware/authenticate.mjs`\n\n```javascript\nexport default async (context) {\n  let headers = context.https.headers;\n  let result = await authenticateUser(headers);\n  if (result) {\n    return true;\n  } else {\n    throw new Error(`401: Unauthorized, failed header validation`);\n  }\n}\n```\n\nThen in your endpoints, you can do the following;\n\n```javascript\nimport authenticate from '../middleware/authenticate.mjs`;\n\nexport async function GET (context) =\u003e {\n  await authenticate(context);\n  return 'authenticated!';\n}\n```\n\n## Roadmap and Feedback\n\nWe're just getting the first release of Instant API into the wild now, we're excited to hear feedback!\nIt's a pretty comprehensive framework we've been developing for years and we're happy with the feature\nset, but please let us know if there are things we are missing by opening an issue.\n\n# Acknowledgements\n\nSpecial thank you to [Scott Gamble](https://x.com/threesided) who helps run all of the front-of-house work for instant.dev 💜!\n\n| Destination | Link |\n| ----------- | ---- |\n| Home | [instant.dev](https://instant.dev) |\n| GitHub | [github.com/instant-dev](https://github.com/instant-dev) |\n| Discord | [discord.gg/puVYgA7ZMh](https://discord.gg/puVYgA7ZMh) |\n| X / instant.dev | [x.com/instantdevs](https://x.com/instantdevs) |\n| X / Keith Horwood | [x.com/keithwhor](https://x.com/keithwhor) |\n| X / Scott Gamble | [x.com/threesided](https://x.com/threesided) |","funding_links":[],"categories":["JavaScript","api"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finstant-dev%2Fapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finstant-dev%2Fapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finstant-dev%2Fapi/lists"}