> Portal Navigation:
> 
> - Append `.md` to any URL under `https://dev.wix.com/docs/` to get its markdown version.
> - Pages are either content pages (article or reference text) or menu pages (a list of links to child pages).
> - To get a menu page, truncate any URL to a parent path and append `.md` (e.g. `https://dev.wix.com/docs/sdk.md`, `https://dev.wix.com/docs/sdk/core-modules.md`).
> - Top-level index of all portals: https://dev.wix.com/docs/llms.txt
> - Full concatenated docs: https://dev.wix.com/docs/llms-full.txt

## Resource: Build an HTTP Endpoint in Studio Two

## Article: Build an HTTP Endpoint in Studio Two

## Article Link: https://dev.wix.com/docs/studio-two/code-your-site/build-a-custom-backend/http-endpoints/build-an-http-endpoint-in-studio-two.md

## Article Content:

# Build an HTTP Endpoint in Studio Two

An [HTTP endpoint](https://dev.wix.com/docs/studio-two/code-your-site/build-a-custom-backend/http-endpoints/about-http-endpoints.md) is a TypeScript file in `src/api/` that handles HTTP requests on Wix servers. You generate the file first, then write handlers that check the caller, do the work, and return a response. Your frontend code calls those handlers.

This article walks through that loop, from a scaffolded endpoint to one that lets logged-in site members post a product review. The handler validates the review and saves it to a CMS collection that site members can't write to directly.

To follow the example, first [create a collection](https://dev.wix.com/docs/studio-two/code-your-site/work-with-data/introduction/create-a-collection.md) with the ID `Reviews` and these fields:

- `productId`: Text.
- `rating`: Number.
- `text`: Text.
- `memberId`: Text.

Set `read` to `ANYONE`, so site visitors can see reviews, and keep `insert`, `update`, and `remove` set to `ADMIN`, so only the endpoint's elevated call can add one.

Follow the instructions below to:

1. Scaffold the endpoint.
2. Set the route.
3. Write the handlers.
4. Check the caller.
5. Call Wix APIs.
6. Call the endpoint from your frontend code.
7. Check the endpoint.
8. Publish your site with the endpoint.

## Step 1 | Scaffold the endpoint

Start from a generated HTTP endpoint rather than an empty file, so the imports and handler types are already in place.

To generate the endpoint, follow Step 1 of [Add an HTTP Endpoint Extension in Studio Two](https://dev.wix.com/docs/studio-two/code-your-site/build-a-custom-backend/http-endpoints/add-an-http-endpoint-extension-in-studio-two.md#step-1--add-an-http-endpoint), and enter `reviews` in **Name**.

You can find the new file at `src/api/reviews.ts`. Wix serves it at the route `reviews`. You spend most of this article in that file.

## Step 2 | Set the route

The endpoint file's path in `src/api/` sets its route. Wix drops the file extension, so `src/api/reviews.ts` serves the route `reviews`. Every `.ts` or `.js` file in `src/api/`, except `.d.ts` files, becomes a route, so keep shared helper code, such as validation functions, outside `src/api/`.

To rename or move a route, rename or move the file. For index files, dynamic segments such as `[reviewId].ts`, and the routes Wix reserves, see [Routes in a Studio Two site](https://dev.wix.com/docs/backend-extensions/http-endpoints/http-endpoint-extension-files-and-code.md#routes-in-a-studio-two-site).

## Step 3 | Write the handlers

Each handler is an exported constant named for the HTTP method it handles, such as `GET`, `POST`, `PUT`, or `DELETE`, and typed as `APIRoute` from `@wix/custom-extensions/types`. A handler receives a context object and returns a standard [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response).

The context object holds the incoming `request`, the request `url`, and the route's dynamic `params`. For what each property contains, see [Handler context](https://dev.wix.com/docs/backend-extensions/http-endpoints/http-endpoint-extension-files-and-code.md#handler-context).

### Errors and logging

Return `Response.json()` to send JSON. To report an expected failure, such as invalid input, set the status yourself. If a handler throws an error that it doesn't catch, the caller gets a `500` response with the body `Internal Server Error`, and the error message stays on the server.

Wix logs uncaught errors from a handler automatically, so you can find their details on the [Logs page](https://dev.wix.com/docs/studio-two/test-your-site/work-with-logs/view-logs.md) in the site dashboard. To record what a handler does, or an error that it catches, [write log entries](https://dev.wix.com/docs/studio-two/test-your-site/work-with-logs/write-log-messages.md).

### Validate the request

To accept a review and reject invalid input with a `400` response, replace the contents of `src/api/reviews.ts` with the following code. The code keeps the generated `APIRoute` import and replaces the generated `GET` and `POST` handlers with a single `POST` handler.

```ts
import type { APIRoute } from '@wix/custom-extensions/types';

export const POST: APIRoute = async ({ request }) => {
  let body;
  try {
    body = await request.json();
  } catch {
    return Response.json({ error: 'Invalid review' }, { status: 400 });
  }

  const productId = body?.productId;
  const rating = body?.rating;
  const text = body?.text;

  const isValid =
    typeof productId === 'string' &&
    Number.isInteger(rating) &&
    rating >= 1 &&
    rating <= 5 &&
    typeof text === 'string' &&
    text.length > 0 &&
    text.length <= 1000;

  if (!isValid) {
    return Response.json({ error: 'Invalid review' }, { status: 400 });
  }

  return Response.json({ valid: true });
};
```

The `try` block around `request.json()` turns a body that isn't valid JSON into a `400` response instead of an uncaught error. The handler doesn't save anything yet. You add the save in [Step 5](#step-5--call-wix-apis).

## Step 4 | Check the caller

An HTTP endpoint has no built-in permissions, so any site visitor can call it. Check who the caller is before you do anything on their behalf.

To read the caller's identity, call [`auth.getTokenInfo()`](https://dev.wix.com/docs/sdk/core-modules/essentials/auth.md#gettokeninfo) from `@wix/essentials`. It returns details from the caller's access token, including:

- `subjectType`: The kind of caller:
  - `VISITOR`: A site visitor who isn't logged in.
  - `MEMBER`: A logged-in site member.
  - `USER`: A Wix user, such as the site owner or a collaborator.
- `subjectId`: The caller's ID, such as the member ID.

For example, to accept reviews only from logged-in site members, import `auth` at the top of `src/api/reviews.ts`:

```ts
import { auth } from '@wix/essentials';
```

Then, add a check at the start of the `POST` handler, before the code that reads the request body:

```ts
const tokenInfo = await auth.getTokenInfo();

if (tokenInfo.subjectType !== 'MEMBER') {
  return Response.json({ error: 'Log in to post a review' }, { status: 403 });
}
```

Check for the identity you allow, rather than ruling out the ones you don't. That way, a caller you didn't plan for is rejected.

When you need the caller's ID, take it from `tokenInfo.subjectId`. Don't trust an ID that the request body or URL sends, because the caller controls those values.

## Step 5 | Call Wix APIs

Wix API calls in a handler run with the caller's identity. A call succeeds only if the caller is allowed to make it. For example, a site visitor can't query the site's contacts, so the call fails with a permissions error.

To run one call with elevated permissions, wrap the method with [`auth.elevate()`](https://dev.wix.com/docs/sdk/core-modules/essentials/auth.md#elevate) from `@wix/essentials`, then call the wrapped method. Only calls through the wrapped method are elevated. The rest of the handler still runs as the caller.

For example, to save the review to the `Reviews` collection, which site members can't add items to, import `items` at the top of `src/api/reviews.ts`:

```ts
import { items } from '@wix/data';
```

Then, in the `POST` handler, replace `return Response.json({ valid: true });` with the following code. It uses `productId`, `rating`, and `text` from Step 3 and `tokenInfo` from Step 4:

```ts
const elevatedInsert = auth.elevate(items.insert);

const review = await elevatedInsert('Reviews', {
  productId,
  rating,
  text,
  memberId: tokenInfo.subjectId,
});

return Response.json({ reviewId: review._id }, { status: 201 });
```

<blockquote class="caution">

__Caution:__
An elevated call ignores the permissions of the caller and of the collection. Before an elevated call, check the caller, as in Step 4, and validate everything that the request sends, as in Step 3.

</blockquote>

To call a 3rd-party API, store its key in the [Secrets Manager](https://dev.wix.com/docs/studio-two/code-your-site/developer-environment/secrets/about-the-secrets-manager.md). In the handler, read the key with [Get Secret Value](https://dev.wix.com/docs/api-reference/business-management/secrets/get-secret-value?apiView=SDK.md) from `@wix/secrets`, wrapped with `auth.elevate()`. Code in an HTTP endpoint runs on Wix servers, so the key never reaches the browser.

### The complete endpoint

Here's the complete `src/api/reviews.ts`, with the handlers from Steps 3 through 5:

```ts
import type { APIRoute } from '@wix/custom-extensions/types';
import { items } from '@wix/data';
import { auth } from '@wix/essentials';

export const POST: APIRoute = async ({ request }) => {
  const tokenInfo = await auth.getTokenInfo();

  if (tokenInfo.subjectType !== 'MEMBER') {
    return Response.json({ error: 'Log in to post a review' }, { status: 403 });
  }

  let body;
  try {
    body = await request.json();
  } catch {
    return Response.json({ error: 'Invalid review' }, { status: 400 });
  }

  const productId = body?.productId;
  const rating = body?.rating;
  const text = body?.text;

  const isValid =
    typeof productId === 'string' &&
    Number.isInteger(rating) &&
    rating >= 1 &&
    rating <= 5 &&
    typeof text === 'string' &&
    text.length > 0 &&
    text.length <= 1000;

  if (!isValid) {
    return Response.json({ error: 'Invalid review' }, { status: 400 });
  }

  const elevatedInsert = auth.elevate(items.insert);

  const review = await elevatedInsert('Reviews', {
    productId,
    rating,
    text,
    memberId: tokenInfo.subjectId,
  });

  return Response.json({ reviewId: review._id }, { status: 201 });
};
```

## Step 6 | Call the endpoint from your frontend code

Call the endpoint from frontend code, such as an Editor React Component (ERC), a Data Source, or a dashboard page, with [`httpClient.fetchWithAuth()`](https://dev.wix.com/docs/sdk/core-modules/essentials/http-client.md#fetchwithauth) from `@wix/essentials`. The method attaches the caller's access token, which every endpoint request needs. A request without a token fails with a `401` response before your handler runs.

### Build the request

Build the endpoint URL from two parts:

- The origin of `import.meta.url`, which is the domain that serves your code. Wix serves endpoints from a different domain than your site's pages.
- The server base path in `import.meta.env.WIX_SERVER_BASE_PATH`, which ends with `/`. If the value isn't set, use `/`.

Add the route after the base path, without a leading `/`. A URL that starts with `/`, such as `'/reviews'`, resolves against the page's own domain, and on a site page it returns a `404` response.

For example, to send a review to the endpoint, add this function to a frontend code file outside `src/api/`, such as `src/lib/post-review.ts`, and import it where you need it:

```ts
import { httpClient } from '@wix/essentials';

export async function postReview(productId: string, rating: number, text: string) {
  const origin = new URL(import.meta.url).origin;
  const basePath = import.meta.env.WIX_SERVER_BASE_PATH || '/';

  const response = await httpClient.fetchWithAuth(`${origin}${basePath}reviews`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ productId, rating, text }),
  });

  if (!response.ok) {
    throw new Error(`The review wasn't saved. Status: ${response.status}`);
  }

  return response.json();
}
```

### Handle the response

`fetchWithAuth()` behaves like `fetch()`, so it doesn't throw an error for a `4xx` or `5xx` response. Check `response.ok` or `response.status`, and show site visitors a message they can act on, such as asking them to log in after a `403` response.

The request body is plain JSON, so send only values that JSON supports. For example, a `Date` arrives as a string, and properties set to `undefined` are dropped.

### Call it from an ERC or Data Source

In an ERC, call a function like `postReview()` from an event handler, such as a button's click handler. In a Data Source, [expose it as a function](https://dev.wix.com/docs/studio-two/code-your-site/code-model/data-sources/build-a-data-source-in-studio-two.md) in the provider's `context.items`, so that components on the page can connect to it.

## Step 7 | Check the endpoint

To check the endpoint:

1. Call `postReview()` from a dashboard page, as in Step 3 of [Add an HTTP Endpoint Extension in Studio Two](https://dev.wix.com/docs/studio-two/code-your-site/build-a-custom-backend/http-endpoints/add-an-http-endpoint-extension-in-studio-two.md#step-3--check-the-endpoint). A dashboard page calls the endpoint as you, a Wix user, not as a site member, so the endpoint returns a `403` response, and `postReview()` throws an error that includes `Status: 403`. That error shows that the caller check works.
2. Publish your site, then post a review as a logged-in site member on the live site. Check that the new item appears in the `Reviews` collection.

> **Note:** In the Studio Two editor and Preview, calls from site code fail with a `403` response while your code changes aren't deployed yet. After you publish your site, the calls work there and on the live site. Calls from a dashboard page work as soon as you save your code.

## Step 8 | Publish your site

When the endpoint behaves the way you want, click **Publish** in the Studio Two editor to ship it with your site.

## See also

- [About HTTP Endpoints](https://dev.wix.com/docs/studio-two/code-your-site/build-a-custom-backend/http-endpoints/about-http-endpoints.md)
- [Add an HTTP Endpoint Extension in Studio Two](https://dev.wix.com/docs/studio-two/code-your-site/build-a-custom-backend/http-endpoints/add-an-http-endpoint-extension-in-studio-two.md)