Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 44 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,40 @@

A public API for global CO2 measurements, powered by the [Ribbit Network](https://ribbitnetwork.org) — an open-source network of citizen-operated CO2 sensors.

## Endpoints
## 📖 Documentation

### `GET /`
Interactive API reference (try requests in the browser):
**[ribbit-api.fly.dev/docs](https://ribbit-api.fly.dev/docs)**

Health check. Returns `🐸`.
Machine-readable OpenAPI spec:
**[ribbit-api.fly.dev/openapi.yaml](https://ribbit-api.fly.dev/openapi.yaml)**

---
The spec also lives in this repo at [`internal/docs/openapi.yaml`](internal/docs/openapi.yaml) and is the source of truth — use it to generate client SDKs (`openapi-generator`, `oapi-codegen`, etc.) or to import into Postman / Insomnia / Bruno.

### `GET /data`
## Quickstart

Returns CO2, temperature, humidity, and location measurements from the sensor network for a given time range.
Once you have an [API key](#api-keys), fetch the last day of CO2 readings:

Requires an API key passed as `Authorization: Bearer <key>` or `X-API-Key: <key>`.

#### Query parameters

| Parameter | Required | Description |
|------------|----------|-------------|
| `start` | yes | Start of time range (RFC 3339, e.g. `2024-01-01T00:00:00Z`) |
| `stop` | no | End of time range (RFC 3339). Omit to query through the present. |
| `hosts` | no | Comma-separated list of sensor IDs to filter by |
| `fields` | no | Comma-separated list of fields to return. Available fields: `co2`, `lat`, `lon`, `humidity`, `baro_pressure`, `baro_temperature`, `alt`. Omit to return all fields. |
| `interval` | no | Aggregate readings into windows of this duration (e.g. `5m`, `1h`). Uses mean aggregation. Omit for raw data. |

#### JSON response

```
GET /data?start=2024-01-01T00:00:00Z&stop=2024-01-02T00:00:00Z&fields=co2,lat,lon&interval=1h
```

```json
{
"data": [
{
"time": "2024-01-01T00:00:00Z",
"host": "a3f2...",
"co2": 412.5,
"lat": 37.77,
"lon": -122.41
},
...
]
}
```sh
curl -H "Authorization: Bearer $RIBBIT_API_KEY" \
"https://ribbit-api.fly.dev/data?start=2024-01-01T00:00:00Z&stop=2024-01-02T00:00:00Z&fields=co2,lat,lon&interval=1h"
```

---

### `GET /sensors`
Endpoints at a glance:

Returns the list of sensor IDs known to the network (over roughly the last 30 days, per InfluxDB's `schema.tagValues` default).
| Endpoint | Auth | Description |
|-----------------|------|-------------|
| `GET /` | — | Health banner (`🐸`) |
| `GET /healthz` | — | Liveness check (`ok`) |
| `GET /docs` | — | Interactive API reference |
| `GET /data` | ✅ | Sensor measurements over a time range |
| `GET /sensors` | ✅ | List of known sensor IDs |

Requires an API key passed as `Authorization: Bearer <key>` or `X-API-Key: <key>`.
See **[/docs](https://ribbit-api.fly.dev/docs)** for full parameter, response, and error documentation.

#### JSON response

```
GET /sensors
```
## Rate limits

```json
{
"sensors": ["a3f2...", "b91c...", "..."]
}
```
Each API key is limited to **1 request per second** with a burst of **60**. Exceeding the limit returns `429 Too Many Requests`.

## Running locally

Expand All @@ -83,12 +53,22 @@ GET /sensors

3. Run:
```sh
go run main.go
go run .
```

The API will be available at `http://localhost:<PORT>`.
The API will be available at `http://localhost:8080`, and the interactive docs at `http://localhost:8080/docs`.

### Previewing just the docs

## Environment variables
If you only want to render the OpenAPI page (no InfluxDB or API-key store needed), run:

```sh
go run . docs
```

This serves the embedded spec and Scalar reference at `http://localhost:8080`. Handy when iterating on [`internal/docs/openapi.yaml`](internal/docs/openapi.yaml).

### Environment variables

| Variable | Description |
|-----------------------|-------------|
Expand All @@ -101,7 +81,7 @@ The API will be available at `http://localhost:<PORT>`.

## API keys

Access to `/data` requires an API key. Keys live in a SQLite file at `API_KEY_DB_PATH`; only the SHA-256 of each key is stored.
Access to `/data` and `/sensors` requires an API key. Keys live in a SQLite file at `API_KEY_DB_PATH`; only the SHA-256 of each key is stored.

Key management is built into the API binary as a `keygen` subcommand.

Expand Down Expand Up @@ -133,6 +113,14 @@ curl -H "Authorization: Bearer rbnt_..." "$API_URL/data?start=2024-01-01T00:00:0
curl -H "X-API-Key: rbnt_..." "$API_URL/data?start=2024-01-01T00:00:00Z"
```

## Updating the docs

The OpenAPI spec at [`internal/docs/openapi.yaml`](internal/docs/openapi.yaml) is embedded into the binary at build time. When you add or change an endpoint:

1. Edit the spec to match.
2. `go build ./...` to verify it still compiles (the spec is `go:embed`-ed).
3. Visit `/docs` locally to spot-check the rendered output.

## Contributing

Feel free to open an issue or PR! We also have enabled the [Github discussion board](https://github.com/Ribbit-Network/api/discussions) if you prefer that.
52 changes: 52 additions & 0 deletions internal/docs/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Package docs serves the OpenAPI specification and a Scalar-rendered API
// reference page.
package docs

import (
_ "embed"
"net/http"
)

//go:embed openapi.yaml
var openAPISpec []byte

const referenceHTML = `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Ribbit Network API — Reference</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E🐸%3C/text%3E%3C/svg%3E" />
</head>
<body>
<script id="api-reference" data-url="/openapi.yaml"></script>
<script>
var configuration = {
theme: "purple",
layout: "modern",
hideDownloadButton: false,
metaData: {
title: "Ribbit Network API",
},
};
document.getElementById("api-reference").dataset.configuration =
JSON.stringify(configuration);
</script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
</body>
</html>
`

// HandleSpec serves the embedded OpenAPI document.
func HandleSpec(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/yaml; charset=utf-8")
w.Header().Set("Cache-Control", "public, max-age=300")
_, _ = w.Write(openAPISpec)
}

// HandleReference serves the Scalar-rendered API reference page.
func HandleReference(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Cache-Control", "public, max-age=300")
_, _ = w.Write([]byte(referenceHTML))
}
Loading
Loading