# opensheet: a Google Sheet as JSON, with no authentication

> A small Bun service that turns a link-shared Google Sheet into JSON rows, addressed by spreadsheet id and tab name. The hosted instance is free, the cache is 30 seconds, and you can run your own.

**benborgers/opensheet** — 💾 An API to get a Google Sheet as JSON, no authentication required. 

- Repository: https://github.com/benborgers/opensheet
- Website: https://opensheet.elk.sh
- Stars: 822 · Forks: 159
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/benborgers-opensheet

## One URL, no token, no OAuth dance

opensheet is a hosted API that returns the rows of a Google Sheet as JSON, and the whole contract is a URL. There is no authentication step, because there is no API key in the request and no OAuth flow to walk through. The service fetches the sheet on Google's behalf and hands back JSON, which means a static site, a script or a small server can read spreadsheet data with an ordinary fetch call. The README describes it as a free and super simple hosted API, and it is a project with real traffic behind it, with the author's own note that the servers take 1.5 billion hits each month and a sponsorship link to cover the hosting cost. The source is MIT licensed, the primary language is TypeScript, and the repository root is small: an index.ts, a Dockerfile, a config directory, a notes directory and a bun.lockb.

## The address is a spreadsheet id and a tab

Two conditions on the Google Sheet have to hold before the API can read it. The first row must be a header row, since that is what the rows are keyed by. Link sharing must be turned on, so that anyone with the link can view the sheet, which is what replaces authentication on the Google side. The URL format is a base address followed by the spreadsheet id and then the tab:

```text
https://opensheet.elk.sh/spreadsheet_id/tab_name
```

A real example from the README looks like this:

```text
https://opensheet.elk.sh/1o5t26He2DzTweYeleXOGiDjlU4Jkx896f95VUHVgS8U/Test+Sheet
```

If you do not know the tab name you can use its position instead, with the tabs counted in the order they are arranged in the sheet:

```text
https://opensheet.elk.sh/1o5t26He2DzTweYeleXOGiDjlU4Jkx896f95VUHVgS8U/1
```

The README includes a warning about that numbering, that the first sheet in order is numbered 1 and not 0, which is the kind of off by one that produces an empty response rather than an error.

## raw mode and a 30 second cache

Two behaviours affect what you get back. The first is formatting. By default Google Sheets formats numbers, dates and other data types, and adding the ?raw=true query parameter returns the underlying values instead.

The second is freshness. Responses are cached for 30 seconds, and the README gives two reasons, to improve performance and to avoid hitting Google Sheets' rate limits. The consequence for a user is explicit, that it might take up to 30 seconds for fresh edits to show up in the API response. That is a reasonable trade for a shared public endpoint, and it is also the feature most likely to surprise someone building an editor that writes to the sheet and immediately reads it back.

## The base URL moved, and the old one still redirects

The hosted instance changed address, and the README explains the consequence. The base URL is now opensheet.elk.sh and not opensheet.vercel.app. The old domain will keep redirecting to the correct URL, so existing links do not break, but the README advises updating your code because the redirect costs a little performance on every call. That is a small operational detail with a practical lesson attached: if you depend on a free hosted service, expect the hostname to move at some point and keep the base address in one place in your configuration rather than hard coding it across templates and scripts.

## Self-hosting means Redis, PostgreSQL and a Google API key

Forks and self-hosted instances need three things, and the README lists them in order. A Redis instance, because the caching layer uses Bun's built-in redis client. A PostgreSQL database, because Bun's built-in sql client is used for analytics, so the service records usage of its own endpoints. And a Google Sheets API key, obtained by creating a project in the Google Cloud Console, searching for Google Sheets API and enabling it, then creating an API key under Credentials, which you can restrict to the Sheets API only. That key is supplied through the GOOGLE_API_KEY environment variable. The separation between the public no-auth endpoint and the credentialed backend is the design: the caller never sees a key, but the server operator does, which is why the hosted instance works without authentication for its users.

## The container and the local dev loop

The deployment artefact is a Dockerfile built on the official Bun image, with a multi stage shape. The install stage copies package.json and bun.lockb and runs a frozen lockfile production install, and the release stage copies the resulting node_modules and index.ts into the final image, sets PORT to 3000, exposes that port and starts the service with the Bun run command. The package manifest shows how little the service depends on, with the dev script running Bun in hot mode against index.ts and the only runtime dependency declared being Bun itself plus its type definitions. For local work the README's instruction is a single command:

```sh
bun run dev
```

The repository is not archived and the last push was on 2026-08-19, with no GitHub releases, so the version you get is whatever the main branch holds.

## Conclusion

opensheet suits a side project, a static site or a prototype that needs to read a public spreadsheet without wiring up Google OAuth, and it suits a self-hosting team that wants the same endpoint on its own infrastructure. It does not suit a production service that needs immediate consistency, since responses are cached for 30 seconds, or a use case involving a sheet that cannot be link shared. Before depending on it, note that the base URL moved to opensheet.elk.sh and that the hosted instance is run by one person asking for sponsorship, so plan for self-hosting if availability matters.

## FAQ

### How do I get a Google Sheet as JSON?

Request https://opensheet.elk.sh/spreadsheet_id/tab_name. The first row of the sheet must be a header row and link sharing must be turned on so anyone with the link can view it.

### Can I use the tab number instead of the tab name?

Yes. Tabs are numbered in the order they are arranged in the sheet, and the first sheet in order is numbered 1, not 0.

### How do I get unformatted values from opensheet?

Add the ?raw=true query parameter. Without it Google Sheets formats numbers, dates and other data types, while raw returns the underlying values.

### How long are opensheet responses cached?

Responses are cached for 30 seconds, for performance and to avoid hitting Google Sheets' rate limits, so a fresh edit can take up to 30 seconds to appear.

### What do I need to self-host opensheet?

A Redis instance for caching, a PostgreSQL database for analytics, and a Google Sheets API key passed in the GOOGLE_API_KEY environment variable. The service itself is a Bun server started with bun run dev locally.

## Sources

- [benborgers/opensheet on GitHub](https://github.com/benborgers/opensheet)
- [Issues](https://github.com/benborgers/opensheet/issues)
- [License: MIT](https://github.com/benborgers/opensheet/blob/main/LICENSE)
- [Project website](https://opensheet.elk.sh)
- [README](https://github.com/benborgers/opensheet/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/benborgers-opensheet
