# Create and manage sites

> Use the Sites API to create, update, and delete sites and their files in Rippling.

Use the Sites API to create, update, and delete sites and their files in Rippling.

## Overview

Sites host browser-based tools inside Rippling, such as dashboards, calculators, and interactive guides. You can build a site with Rippling AI or upload HTML, CSS, and JavaScript that you built with another tool. Each site runs at `app.rippling.com/sites/<api_name>`, and only the people you give access can see it.

Use the Sites API to create and update sites from code, a [function](https://developer.rippling.com/documentation/rest-api/guides/functions), or an AI agent. A site can also call the Rippling Platform APIs to read and write Rippling data.

Use a site when you need a flexible, polished experience, like a guide, calculator, or executive readout. Use a custom app when you need structured records, forms, approvals, and role-based permissions.

_Key terms:_

-   **Site**: A web app hosted in Rippling. A site is a single `.html` file or a set of files with an entry page, usually `index.html`.
-   **API name**: The unique identifier for a site, used in its URL and in API requests. You set it when you create a site with the API. In Site Manager, Rippling generates it from the display name.
-   **Display name**: The name people see in the Rippling sidebar and search results. The API returns it as `name`.
-   **Bundle**: The set of files that's live on a site. Each site has one active bundle, identified by `current_bundle_id`. Each time you replace or change the files, Rippling creates a new bundle.
-   **Staged upload**: Files you've uploaded but haven't attached to a site yet. Each staged upload has a `file_id` and expires 1 hour after you presign it.
-   **Entry path**: The page that loads first when someone opens the site. Defaults to `index.html`.
-   **Admins** and **Viewers**: The groups that can edit and view the site.

### Available endpoints

| Endpoint | Description |
| --- | --- |
| [GET List sites](https://developer.rippling.com/documentation/rest-api/reference/list-sites) | Returns the sites you can view or edit. |
| [GET Retrieve a specific site](https://developer.rippling.com/documentation/rest-api/reference/get-sites) | Returns one site by its API name. |
| [POST Create a presigned staged upload batch](https://developer.rippling.com/documentation/rest-api/reference/upload-batches-presign-sites) | Returns upload URLs for your site files. |
| [POST Create a new site](https://developer.rippling.com/documentation/rest-api/reference/create-sites) | Creates a site from a staged upload. |
| [PATCH Update a site](https://developer.rippling.com/documentation/rest-api/reference/update-sites) | Updates a site's details or replaces all of its files. |
| [GET List files in the active bundle](https://developer.rippling.com/documentation/rest-api/reference/files-sites) | Returns the files and folders in a site. |
| [POST Apply file operations to a site bundle](https://developer.rippling.com/documentation/rest-api/reference/file-operations-sites) | Adds, renames, moves, or deletes individual files and folders. |
| [DELETE Delete a site](https://developer.rippling.com/documentation/rest-api/reference/delete-sites) | Deletes a site. |

### Suggested workflow

1.  Call [POST Create a presigned staged upload batch](https://developer.rippling.com/documentation/rest-api/reference/upload-batches-presign-sites) to get an upload URL.
2.  Upload your `.html` or `.zip` file to that URL.
3.  Call [POST Create a new site](https://developer.rippling.com/documentation/rest-api/reference/create-sites) with the `file_id` from step 1.
4.  To change the site later, call [PATCH Update a site](https://developer.rippling.com/documentation/rest-api/reference/update-sites) or [POST Apply file operations to a site bundle](https://developer.rippling.com/documentation/rest-api/reference/file-operations-sites).
5.  Call [DELETE Delete a site](https://developer.rippling.com/documentation/rest-api/reference/delete-sites) when you no longer need it.

* * *

## Requirements

-   Rippling AI. Companies with Rippling AI automatically get access to the Rippling APIs, including the Sites API.
-   An API token with the following scopes:
    -   `sites.read` to fetch sites and their files
    -   `sites.read-write` to create, update, and delete sites and their files

* * *

## Fetch

### List sites

Use [GET List sites](https://developer.rippling.com/documentation/rest-api/reference/list-sites) to return the sites you have view or edit access to. Results are paginated.

_Request:_

```bash
curl -L -X GET 'https://rest.ripplingapis.com/sites/?limit=50' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer REDACTED'
```

_Response:_

```json
{
  "__meta": {
    "redacted_fields": []
  },
  "results": [
    {
      "id": "6748f1a2b3c4d5e6f7a8b9c0",
      "name": "Employee Handbook",
      "description": "Searchable company handbook",
      "api_name": "employee_handbook",
      "logo": null,
      "current_bundle_id": "6748f1a2b3c4d5e6f7a8b9d1",
      "admin_group_id": "6748f1a2b3c4d5e6f7a8b9e2",
      "viewer_group_id": "6748f1a2b3c4d5e6f7a8b9f3",
      "created_by": "6748f1a2b3c4d5e6f7a8ba04",
      "created_at": "2026-10-22T14:30:00Z"
    }
  ],
  "next_link": null
}
```

| Response field | Description |
| --- | --- |
| `__meta.redacted_fields` | Fields removed from the response because you don't have permission to see them. |
| `results[].id` | The site's ID. |
| `results[].name` | The site's display name. |
| `results[].description` | The site's description, if it has one. |
| `results[].api_name` | The site's unique API name, used in its URL. |
| `results[].logo` | The URL of the site's logo, hosted by Rippling. |
| `results[].current_bundle_id` | The ID of the site's active bundle. |
| `results[].admin_group_id` | The ID of the group that can edit the site. |
| `results[].viewer_group_id` | The ID of the group that can view the site. |
| `results[].created_by` | The ID of the role that created the site. |
| `results[].created_at` | The date and time the site was created. |
| `next_link` | A link to the next page of results. `null` on the last page. |

### Retrieve a site

Use [GET Retrieve a specific site](https://developer.rippling.com/documentation/rest-api/reference/get-sites) to return one site by its API name. The `name` filter on List sites matches any part of the display name, so use this endpoint when you need one exact site.

_Request:_

```bash
curl -L -X GET 'https://rest.ripplingapis.com/sites/employee_handbook/' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer REDACTED'
```

_Response:_

```json
{
  "id": "6748f1a2b3c4d5e6f7a8b9c0",
  "name": "Employee Handbook",
  "description": "Searchable company handbook",
  "api_name": "employee_handbook",
  "logo": null,
  "current_bundle_id": "6748f1a2b3c4d5e6f7a8b9d1",
  "admin_group_id": "6748f1a2b3c4d5e6f7a8b9e2",
  "viewer_group_id": "6748f1a2b3c4d5e6f7a8b9f3",
  "created_by": "6748f1a2b3c4d5e6f7a8ba04",
  "created_at": "2026-10-22T14:30:00Z"
}
```

The fields are the same as a result in [List sites](https://developer.rippling.com/documentation/rest-api/guides/sites#list-sites).

### List a site's files

Use [GET List files in the active bundle](https://developer.rippling.com/documentation/rest-api/reference/files-sites) to return the files and folders in a site's active bundle.

_Request:_

```bash
curl -L -X GET 'https://rest.ripplingapis.com/sites/employee_handbook/files/?recursive=true' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer REDACTED'
```

_Response:_

```json
{
  "bundle_id": "6748f1a2b3c4d5e6f7a8b9d1",
  "entry_path": "index.html",
  "parent_path": "",
  "recursive": true,
  "files": [
    {
      "path": "index.html",
      "name": "index.html",
      "parent_path": "",
      "content_type": "text/html",
      "size_bytes": 13517,
      "sha256": "9f2c4e1b7a..."
    },
    {
      "path": "assets/app.css",
      "name": "app.css",
      "parent_path": "assets",
      "content_type": "text/css",
      "size_bytes": 9420,
      "sha256": "4b7d0a93e2..."
    }
  ],
  "directories": [
    {
      "path": "assets",
      "name": "assets",
      "parent_path": ""
    }
  ]
}
```

| Response field | Description |
| --- | --- |
| `bundle_id` | The ID of the active bundle. Use it as `base_bundle_id` when you change files. |
| `entry_path` | The page that loads first. |
| `parent_path` | The folder that was listed. |
| `recursive` | Whether subfolders were included. |
| `files[].path` | The file's full path in the site. |
| `files[].name` | The file's name. |
| `files[].parent_path` | The folder the file is in. |
| `files[].content_type` | The file's media type, like `text/html`. |
| `files[].size_bytes` | The file's size in bytes. |
| `files[].sha256` | A SHA-256 hash of the file's contents. Use it to check whether a file changed. |
| `directories[].path` | The folder's full path. |
| `directories[].name` | The folder's name. |
| `directories[].parent_path` | The folder it's inside. |

* * *

## Create

Creating a site takes three steps: get an upload URL, upload your file to it, and then create the site.

A site can include HTML, CSS, and JavaScript files, plus other assets like images and PDFs. A site can be up to 25 MB and include up to 200 files. If you upload a `.zip` without an `index.html` file, set `entry_path` to the page that should load first. In Site Manager, the upload fails with "Entry path index.html was not found."

### Get an upload URL

Use [POST Create a presigned staged upload batch](https://developer.rippling.com/documentation/rest-api/reference/upload-batches-presign-sites) to get a URL for each file you plan to upload. You use this same step to create a site, replace a site's files, or add a file.

_Request:_

```bash
curl -L -X POST 'https://rest.ripplingapis.com/sites/upload-batches/presign/' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer REDACTED'
```

_Request body:_

```json
{
  "file_intents": [
    {
      "client_ref": "handbook-upload-1",
      "file_name": "site.zip"
    }
  ]
}
```

| Request body field | Required | Description |
| --- | --- | --- |
| `file_intents` | Yes | The files you plan to upload. |
| `file_intents[].client_ref` | Yes | Any unique ID you choose, so you can match each file to its upload URL. |
| `file_intents[].file_name` | Yes | The file's name, like `site.zip` or `index.html`. |

_Response:_

```json
{
  "upload_batch_id": "6748f1a2b3c4d5e6f7a8bb00",
  "files": [
    {
      "client_ref": "handbook-upload-1",
      "file_id": "6748f1a2b3c4d5e6f7a8bb15",
      "file_name": "site.zip",
      "upload_url": "https://example-bucket.s3.amazonaws.com/",
      "upload_fields": {
        "key": "staged/6748f1a2b3c4d5e6f7a8bb15/site.zip",
        "policy": "REDACTED",
        "x-amz-signature": "REDACTED"
      }
    }
  ]
}
```

| Response field | Description |
| --- | --- |
| `upload_batch_id` | The ID of the staged upload batch. You don't use it in later calls. |
| `files[].client_ref` | The `client_ref` you sent, so you can match each file. |
| `files[].file_id` | The staged upload ID. Use it to create or update the site. |
| `files[].file_name` | The file name you sent. |
| `files[].status` | The upload's status. |
| `files[].upload_url` | The URL to upload the file to. |
| `files[].upload_fields` | The form fields to send with the upload. The fields can vary, so send all of them exactly as returned. |

Use file\_id, not upload\_batch\_id

Pass `files[].file_id` to the create, update, and file operations endpoints. Passing `upload_batch_id` doesn't work.

### Upload the file

Send a multipart `POST` to `upload_url`. Include every field from `upload_fields`, and then add the file as the last part. This request goes straight to storage, so don't include your Rippling token.

```bash
curl -L -X POST 'https://example-bucket.s3.amazonaws.com/' \
-F 'key=staged/6748f1a2b3c4d5e6f7a8bb15/site.zip' \
-F 'policy=REDACTED' \
-F 'x-amz-signature=REDACTED' \
-F 'file=@site.zip'
```

### Create the site

Use [POST Create a new site](https://developer.rippling.com/documentation/rest-api/reference/create-sites) to create the site from your staged upload.

_Request:_

```bash
curl -L -X POST 'https://rest.ripplingapis.com/sites/' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer REDACTED'
```

_Request body:_

```json
{
  "name": "Employee Handbook",
  "description": "Searchable company handbook",
  "api_name": "employee_handbook",
  "file_id": "6748f1a2b3c4d5e6f7a8bb15"
}
```

| Request body field | Required | Description |
| --- | --- | --- |
| `name` | Yes | The site's display name. |
| `api_name` | Yes | The site's unique API name, used in its URL. |
| `file_id` | Yes | The staged upload ID from the presign response. |
| `description` | No | A description of the site. |
| `logo` | No | A Rippling-hosted logo URL. |
| `entry_path` | No | The page that loads first, like `home.html`. Required if your `.zip` doesn't include `index.html`. |

_Response:_

```json
{
  "id": "6748f1a2b3c4d5e6f7a8b9c0",
  "name": "Employee Handbook",
  "description": "Searchable company handbook",
  "api_name": "employee_handbook",
  "logo": null,
  "current_bundle_id": "6748f1a2b3c4d5e6f7a8b9d1",
  "admin_group_id": "6748f1a2b3c4d5e6f7a8b9e2",
  "viewer_group_id": "6748f1a2b3c4d5e6f7a8b9f3",
  "created_by": "6748f1a2b3c4d5e6f7a8ba04",
  "created_at": "2026-10-27T10:30:00Z"
}
```

The fields are the same as a result in [List sites](https://developer.rippling.com/documentation/rest-api/guides/sites#list-sites).

Reusing a staged upload

You can use the same `file_id` for up to 1 hour after you presign it. Each time you use it, Rippling copies the files into a new bundle. After an hour, the staged upload is deleted.

* * *

## Update

### Update a site's details

Use [PATCH Update a site](https://developer.rippling.com/documentation/rest-api/reference/update-sites) to change a site's name, description, or logo. You don't need to upload anything for these changes.

_Request:_

```bash
curl -L -X PATCH 'https://rest.ripplingapis.com/sites/employee_handbook/' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer REDACTED'
```

_Request body:_

```json
{
  "name": "Employee Handbook 2027",
  "description": "Updated for the new year"
}
```

| Request body field | Required | Description |
| --- | --- | --- |
| `name` | No | The site's new display name. |
| `description` | No | The site's new description. |
| `logo` | No | A new Rippling-hosted logo URL. |
| `file_id` | No | A staged upload ID. Replaces all of the site's files. |
| `entry_path` | No | The page that loads first. Required with `file_id` if the new `.zip` doesn't include `index.html`. |

_Response:_

```json
{
  "id": "6748f1a2b3c4d5e6f7a8b9c0",
  "name": "Employee Handbook 2027",
  "description": "Updated for the new year",
  "api_name": "employee_handbook",
  "logo": null,
  "current_bundle_id": "6748f1a2b3c4d5e6f7a8b9d1",
  "admin_group_id": "6748f1a2b3c4d5e6f7a8b9e2",
  "viewer_group_id": "6748f1a2b3c4d5e6f7a8b9f3",
  "created_by": "6748f1a2b3c4d5e6f7a8ba04",
  "created_at": "2026-10-22T14:30:00Z"
}
```

The fields are the same as a result in [List sites](https://developer.rippling.com/documentation/rest-api/guides/sites#list-sites).

### Replace all of a site's files

To replace the whole site, [get an upload URL](https://developer.rippling.com/documentation/rest-api/guides/sites#get-an-upload-url), [upload the file](https://developer.rippling.com/documentation/rest-api/guides/sites#upload-the-file), and then call [PATCH Update a site](https://developer.rippling.com/documentation/rest-api/reference/update-sites) with the new `file_id`:

```json
{
  "file_id": "6748f1a2b3c4d5e6f7a8bb27"
}
```

Everyone with access sees the new version right away.

### Change individual files

Use [POST Apply file operations to a site bundle](https://developer.rippling.com/documentation/rest-api/reference/file-operations-sites) to add, rename, move, or delete specific files and folders. All operations in one request succeed or fail together.

To add or replace a file, [get an upload URL](https://developer.rippling.com/documentation/rest-api/guides/sites#get-an-upload-url) and [upload the file](https://developer.rippling.com/documentation/rest-api/guides/sites#upload-the-file) first, then use its `file_id` in a `put` operation.

_Request:_

```bash
curl -L -X POST 'https://rest.ripplingapis.com/sites/employee_handbook/file-operations/' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer REDACTED'
```

_Request body:_

```json
{
  "base_bundle_id": "6748f1a2b3c4d5e6f7a8b9d1",
  "operations": [
    {
      "op": "put",
      "kind": "file",
      "path": "assets/logo.png",
      "file_id": "6748f1a2b3c4d5e6f7a8bb31"
    },
    {
      "op": "rename",
      "kind": "file",
      "from_path": "about.html",
      "to_path": "team.html"
    },
    {
      "op": "delete",
      "kind": "directory",
      "path": "old-assets",
      "recursive": true
    }
  ]
}
```

| Request body field | Required | Description |
| --- | --- | --- |
| `base_bundle_id` | Yes | The site's `current_bundle_id`. |
| `operations` | Yes | The changes to make. Must include at least one. |
| `operations[].op` | Yes | `put`, `rename`, `move`, or `delete`. Folders support `put`, `rename`, and `delete`, but not `move`. |
| `operations[].kind` | Yes | `file` or `directory`. |
| `operations[].path` | For `put` and `delete` | The path of the file or folder. |
| `operations[].file_id` | For `put` on a file | The staged upload ID for the file. |
| `operations[].from_path` | For `rename` and `move` | The current path. |
| `operations[].to_path` | For `rename` and `move` | The new path. |
| `operations[].recursive` | No | For deleting a folder. Set to `true` to delete everything inside it. |
| `entry_path` | No | A new entry page. If you leave it out, the current one stays. |

_Response:_

```json
{
  "id": "6748f1a2b3c4d5e6f7a8b9c0",
  "name": "Employee Handbook",
  "description": "Searchable company handbook",
  "api_name": "employee_handbook",
  "logo": null,
  "current_bundle_id": "6748f1a2b3c4d5e6f7a8b9e7",
  "admin_group_id": "6748f1a2b3c4d5e6f7a8b9e2",
  "viewer_group_id": "6748f1a2b3c4d5e6f7a8b9f3",
  "created_by": "6748f1a2b3c4d5e6f7a8ba04",
  "created_at": "2026-10-22T14:30:00Z"
}
```

The response is the updated site, including its new `current_bundle_id`.

Use the current bundle ID

If `base_bundle_id` doesn't match the site's current bundle, the request returns `409`. This happens when someone else changed the site after you fetched it. Get the latest `current_bundle_id` and try again.

* * *

## Delete

Use [DELETE Delete a site](https://developer.rippling.com/documentation/rest-api/reference/delete-sites) to delete a site.

_Request:_

```bash
curl -L -X DELETE 'https://rest.ripplingapis.com/sites/employee_handbook/' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer REDACTED'
```

A successful delete returns `204 No Content` with no response body.

* * *

## Use cases

### Call Rippling APIs from a site

A site can read and write Rippling data through the Platform APIs. Each viewer only sees the data their own Rippling permissions allow. For example, a manager viewing a compensation benchmarking site only sees compensation for their direct reports. Data saved in the site's files, like hard-coded values or a bundled CSV, isn't filtered. Every viewer can see it.

A site can only use fields that the Rippling REST APIs expose. Before you build a site around a field, check that the field is available in the relevant API and that it returns the right data for each viewer. There are no cross-origin resource sharing (CORS) limits on calls from a site to `rest.ripplingapis.com`.

A site that calls `POST`, `PATCH`, or `DELETE` endpoints changes live data in Rippling. Before the call goes through, the viewer sees a consent notice and must click **I understand**. The notice tells them the site can change live data and can only use permissions they already have. If they click **Go back**, the notice closes and the request isn't sent. Viewers see the notice once for each version of the site. If you update a site and it still makes write calls, viewers see the notice again.

To build a site that writes data, you need the **Sites creator with write API access** permission. Everyone has it by default, and admins can remove it in the Permissions app. A site can only write if the person who created it or last updated it has this permission.

Write actions use the viewer's permissions

A site can't do anything the viewer couldn't do in Rippling themselves. If a viewer doesn't have permission to update a record, the site can't update it for them.

You don't create or store a token for this. When someone views your site, Rippling handles authentication for every `fetch` or `XMLHttpRequest` call the site makes to `rest.ripplingapis.com`. It creates a token for the viewer, sends the request, and returns the response to your site.

1.  Call the endpoints your site needs with a standard `fetch` call to `https://rest.ripplingapis.com`. Don't add an `Authorization` header.
2.  Handle errors for calls the viewer doesn't have permission to make.
3.  (Optional) To show information about the viewer, such as their work email, call [GET Retrieve my SSO information](https://developer.rippling.com/documentation/rest-api/reference/list-sso-me).

Customize the following code for your setup:

```javascript
async function listWorkers() {
  const response = await fetch('https://rest.ripplingapis.com/workers/?limit=50', {
    headers: { Accept: 'application/json' },
  });

  if (!response.ok) {
    throw new Error(`Couldn't load workers: ${response.status}`);
  }

  const data = await response.json();
  return data.results;
}

listWorkers().then((workers) => {
  const list = document.querySelector('#workers');
  for (const worker of workers) {
    const item = document.createElement('li');
    item.textContent = worker.work_email;
    list.append(item);
  }
});
```

Rippling only handles authentication when the site is viewed at `app.rippling.com/sites/<api_name>`. If you open the site's files directly on your computer, its API calls fail. Use sample data while you build, then test API calls after you upload the site.

Don't add your own token

Never put an API token in a site's HTML or JavaScript. Anyone who can view the site can read its code. Rippling replaces any token you add with the viewer's, so it doesn't change what the site can do.

### Deploy a site from a function or agent

1.  Build the site files with your coding agent or a [function](https://developer.rippling.com/documentation/rest-api/guides/functions).
2.  The first time, [get an upload URL](https://developer.rippling.com/documentation/rest-api/guides/sites#get-an-upload-url), upload the files, and [create the site](https://developer.rippling.com/documentation/rest-api/guides/sites#create-the-site).
3.  After that, [replace all of the site's files](https://developer.rippling.com/documentation/rest-api/guides/sites#replace-all-of-a-sites-files) or [change individual files](https://developer.rippling.com/documentation/rest-api/guides/sites#change-individual-files).

* * *

## Limits

Sites run in a sandboxed frame inside Rippling, which limits what they can do:

-   **Open external pages inside the site.** Links to other websites need to open in a new tab, for example with `target="_blank"`.
-   **Load content from other websites.** A site can't call APIs or load images from other domains, such as map tiles.
-   **Read the query string.** A site can't read the query parameters in the Rippling URL.
-   **Store data in the browser.** `localStorage`, `sessionStorage`, and cookies aren't available. Code that uses them throws an error.
-   **Use any image, font, or audio source.** Images can use `blob:` and `data:` URLs. Fonts can use `data:` URLs only. Audio from `blob:` and `data:` URLs doesn't play.
-   **Manage sites.** A site can't call the Sites API. Those requests return `403`.
-   **Launch from an app tile.** Sites appear in the **Sites** section of the sidebar.
-   **Ship in a package.** You can't include a site in a distributable package.

The Sites API has no endpoint-specific rate limits. It follows the standard [API limits](https://developer.rippling.com/documentation/rest-api/essentials/api-limits): 600 requests per 10 seconds per token for each endpoint path. If you go over, the API returns `429 Too Many Requests`. Retry with exponential backoff or the `Retry-After` header.

* * *

## See also

-   [Control who can create and manage sites](https://help.rippling.com/article?articleId=6HIjroSoHuOu6jAFO1vE95) in the Help Center
-   [API tokens](https://developer.rippling.com/documentation/rest-api/essentials/api-tokens)
-   [Functions guide](https://developer.rippling.com/documentation/rest-api/guides/functions)

Source: https://developer.rippling.com/documentation/rest-api/guides/sites

> For the complete documentation index, see [llms.txt](https://developer.rippling.com/llms.txt). Public pages have a Markdown version at the same URL with .md added.
