# How Renovara SQL works

> The Renovara SQL API runs read-only DuckDB SQL over Australian electricity and gas market data from AEMO, from your own code and AI agents. Below is the whole API, its endpoints, sign-in, how statements run, the SQL dialect, limits, errors and the tables.

This is the markdown copy of https://renovara.co/how-renovara-sql-works, generated from the same source as that page.

## What the Renovara SQL API is

The Renovara SQL API gives an organisation's own software read-only SQL access to the Australian National Electricity Market (NEM) and east coast gas market tables that Renovara holds. The data is AEMO's published market data, cleaned and refreshed daily: electricity dispatch at AEMO's five-minute interval, much of it from January 2019, and AEMO Gas Bulletin Board data by gas day.

You send one DuckDB `SELECT` over HTTPS. A quick query answers in the same call; a slow one returns a statement ID to poll. Results come back as rows, and every result is also a Parquet file.

It is built for organisations that serve many clients and systems from their own code: client reporting and data products, scheduled jobs and notebooks, and AI agents that write and run their own SQL. It is an enterprise plan, separate from the subscription that connects Claude or ChatGPT to Renovara through an MCP server (see [How Renovara works](https://renovara.co/how-renovara-works)). Both read the same tables and the same analyst guide, so a table means the same thing in each. The plan and its trial are described at [renovara.co/sql-api](https://renovara.co/sql-api).

## Where to start

An agent or program needs nothing but the base URL to learn the API and the data. These calls need no credentials:

1. `GET https://sql.api.renovara.co/v1/` says what the service is, the SQL dialect and its version, how to sign in, the trial's limits, and links to everything below.
2. `GET /v1/guide?domain=nem` (or `domain=gas`) returns the analyst guide: which table answers which question, the traps in each table, worked SQL, and the DuckDB translations. Read it before the first query.
3. `GET /v1/tables` lists every table; `GET /v1/tables/{table}` gives one table's columns and their meaning.
4. With an access token, `POST /v1/statements` runs a query.

The API's own description is OpenAPI 3.1 at `https://sql.api.renovara.co/v1/openapi.json`, with a plain-English description of every operation, field and error. Agent frameworks can build tools straight from it.

## Base URL and endpoints

The base URL is `https://sql.api.renovara.co/v1`. Every path below is relative to it. Requests and responses are JSON, except the guide, which is markdown.

### Public routes

These need no credentials.

| Route | What it returns |
| --- | --- |
| `GET /` | What the service is, the SQL dialect and version, the market time zone, how to authenticate (token URL, scope, where to get credentials), the trial's limits, and links to the routes below. |
| `GET /openapi.json` | The OpenAPI 3.1 description of the API. |
| `GET /.well-known/oauth-protected-resource` | OAuth 2.0 Protected Resource Metadata (RFC 9728): the authorization server that issues tokens for this API and the scope to ask for. |
| `GET /guide?domain=nem` | The analyst guide as markdown. `domain` is `nem` (electricity, the default) or `gas`. |
| `GET /tables` | Every table you can query, with a one-line description. |
| `GET /tables/{table}` | One table: its columns with their types and meaning, primary key, update frequency and measured coverage. |
| `GET /reference?path=references/schema-index.md` | A reference file the guide points to, by its `references/` path. Optional `domain` (`nem` or `gas`). |

### Statement routes

These need an access token (see [Authentication](https://renovara.co/how-renovara-sql-works#authentication)).

| Route | What it does |
| --- | --- |
| `POST /statements` | Runs one read-only SQL statement and waits briefly for its result. |
| `GET /statements/{statement_id}` | A statement's state, and its result once it has succeeded. |
| `POST /statements/{statement_id}/cancel` | Stops a pending or running statement. |

### Credentials requested by an agent

These need no credentials. See [Credentials for an agent's person](https://renovara.co/how-renovara-sql-works#credentials-for-an-agents-person).

| Route | What it does |
| --- | --- |
| `POST /trial/requests` | Asks for credentials (and a trial, if needed) for a named person, who approves. |
| `GET /trial/requests/{device_code}` | The request's status; once approved, the credentials, returned once. |

## Authentication

The API uses OAuth 2.0 client credentials.

1. **Get credentials.** On the [Renovara credentials page](https://renovara.co/sql-api-credentials), which the welcome email links to, the person who manages your organisation's account creates a set of credentials and downloads them as a file, `renovara-sql.json`. The secret is shown once and never emailed. An organisation can hold several sets, one per system, and revoke any of them.
2. **Exchange them for an access token** at the token URL, `https://auth.api.renovara.co/oauth2/token`, with `grant_type=client_credentials` and scope `renovara-sql/query`. The token URL is also in your credentials file and in `GET /v1/` under `authentication.token_url`.
3. **Send the token** on every statement call as `Authorization: Bearer <token>`.

A token lasts 60 minutes. Cache it and reuse it for its hour, then fetch a new one: tokens are issued at a cost, so fetching one per query is wasteful and slower. The Python client does this for you.

The credentials file looks like this. Keep it private: anyone holding it can query as your organisation.

```json
{
  "api_url": "https://sql.api.renovara.co/v1/",
  "token_url": "https://auth.api.renovara.co/oauth2/token",
  "client_id": "...",
  "client_secret": "...",
  "scope": "renovara-sql/query"
}
```

### Credentials for an agent's person

An AI agent without credentials can ask for them on its person's behalf. It never signs anyone up itself: the person approves.

1. The agent sends `POST /v1/trial/requests` with `{"email": "<the person's work email>", "agent": "<the agent's name>", "credentials_name": "<a name for the set>"}`. Nothing is emailed.
2. The response has a `user_code`, a link (`verification_uri_complete`) and a `device_code`. The agent shows the person the link, which opens [renovara.co/sql-api-approve](https://renovara.co/sql-api-approve). The `device_code` is the agent's secret: never show it.
3. The person signs in as the email the agent named, sees which agent asked and for what, and approves or denies. If they have no organisation, approving first sends them through Stripe Checkout to start a 30-day free trial (no card; Stripe asks for an address for tax), then back to the request, which is approved once the trial is set up. Until then the agent's poll answers `pending`.
4. The agent polls `GET /v1/trial/requests/{device_code}` every `interval` seconds. Once approved, the response holds the credentials, in the same form as the file above, returned once.

A request expires after 30 minutes.

## Running a statement

Statements are submit-and-poll, so a long query never holds a connection open.

1. **Submit.** `POST /v1/statements` with `{"sql": "SELECT ...", "wait_seconds": 20}`. `wait_seconds` is how long the call waits for the result, from 0 to 25; the default is 20. The SQL can be up to 100,000 bytes.
2. **Read the state.** If the statement finished in that time, the response has `"state": "SUCCEEDED"` and the result, or `"state": "FAILED"` and an `error`. Otherwise it is `PENDING` or `RUNNING`, with a `statement_id`.
3. **Poll.** `GET /v1/statements/{statement_id}`, with a short pause between calls, until the state is `SUCCEEDED` or `FAILED`.
4. **Cancel if you need to.** `POST /v1/statements/{statement_id}/cancel`. Its `cancel` field is `cancelled`, `requested` (the query stops within a few seconds; keep polling) or `finished` (it had already finished).

A statement is visible only to the client that submitted it, and lasts 24 hours. A statement's response carries `statement_id`, `state`, `submitted_at` (Unix seconds), `started_at`, `finished_at` and `query_ms` once known, then `result` or `error`.

**Retries.** Send an `Idempotency-Key` header (1 to 200 characters) with each submit. A retry with the same key returns the statement already made instead of running, and counting, it twice. Use a new key for each new statement and the same key only to retry it. The Python client sends one for you.

**Dry runs.** `EXPLAIN SELECT ...` returns DuckDB's query plan without running the query. `EXPLAIN ANALYZE SELECT ...` runs the query and returns the plan with its timings, not the rows.

**Usage.** Each submit's response includes `usage`: your plan, the query time used and allowed, statements today and the daily limit, the statements you may run at once and, on a trial, when it ends.

## Results

- `result.columns` lists each column's `name` and `type`; `result.row_count` is the number of rows.
- A result of up to 10,000 rows and 4 MB comes back inline as `result.rows`, an array of rows, each an array of values in column order, with `rows_included: true`.
- Every successful result is also a Parquet file at `result.parquet_url`. The link works for an hour; get the statement again for a fresh link. Results last 24 hours.
- A result over 5,000,000 rows is refused with `RESULT_TOO_LARGE`. Aggregate in SQL instead.
- A statement stops at 300 seconds with `QUERY_TIMEOUT`. Narrow the time range or the units, or aggregate before joining; see [Writing fast queries](https://renovara.co/how-renovara-sql-works#writing-fast-queries).

## DuckDB SQL

The API runs DuckDB, not Databricks SQL. The exact version is in `GET /v1/` under `dialect`. The analyst guide's worked examples are written in Databricks SQL, because the guide is shared with Renovara's other services; the guide served by this API opens with the translations below, which override it where they differ.

- **One read-only `SELECT` per statement**, or `EXPLAIN` / `EXPLAIN ANALYZE` of one. Anything else is refused with `QUERY_REJECTED`, as is a call to a function that reports the server's configuration.
- **Table names** work bare (`silver_dispatchis_reports_dispatch_price`) or fully qualified (`external_data.nemweb.silver_dispatchis_reports_dispatch_price`).
- **`SETTLEMENT_DAY`.** Every time-series table has this extra integer column first, so `SELECT *` returns one more column than the same table does through the MCP subscription's Databricks SQL. See [Writing fast queries](https://renovara.co/how-renovara-sql-works#writing-fast-queries).
- **Market time.** Market timestamps, such as `SETTLEMENTDATE`, carry no time zone: they are plain `TIMESTAMP` values holding AEST wall-clock time (Australia/Brisbane, UTC+10, no daylight saving). Do not shift them. Columns ending `_UTC`, such as `SETTLEMENTDATE_UTC`, are real moments, `TIMESTAMP WITH TIME ZONE`, shown with `+00`. The session runs in UTC, so `now()` is UTC: "the last hour" is `SETTLEMENTDATE_UTC >= now() - INTERVAL 1 HOUR`, and "now" in market time is `CAST(current_timestamp AS TIMESTAMP) + INTERVAL 10 HOUR`.
- **Quotes.** Strings take single quotes. Double quotes name columns.
- **NULLs.** `ORDER BY` puts NULLs last by default. Databricks puts them first in ascending order.
- **Rounding** and approximate percentiles can differ from Databricks in the last digit.
- **Traps.** The guide sets out each table's traps, such as intervention pricing and market time. Read it before the first query.

| Databricks SQL | DuckDB |
| --- | --- |
| `unix_timestamp(ts)` | `epoch(ts)` |
| `date_format(ts, 'yyyy-MM')` | `strftime(ts, '%Y-%m')` |
| `to_timestamp(s, fmt)` | `strptime(s, fmt)`, with `%`-style formats |
| `date_add(d, n)` | `d + INTERVAL (n) DAY` |
| `collect_list(x)` | `list(x)` |
| `collect_set(x)` | `list(DISTINCT x)` |
| `percentile_approx(x, p)` | `approx_quantile(x, p)` |
| `named_struct(...)` | `struct_pack(...)` |
| `from_utc_timestamp(current_timestamp(), 'Australia/Brisbane')` | `CAST(current_timestamp AS TIMESTAMP) + INTERVAL 10 HOUR` |

`date_trunc`, `max_by`, `min_by`, `hour`, `year`, `concat_ws`, `coalesce`, `QUALIFY` and window functions work as written.

## Writing fast queries

Every time-series table the SQL API serves is a plain copy whose first column is `SETTLEMENT_DAY`: an `INTEGER` in the form `yyyymmdd` (for example `20261020`) holding the date of the table's main timestamp. That timestamp is whichever of `SETTLEMENTDATE`, `INTERVAL_DATETIME`, `DATETIME`, `GasDate`, `EFFECTIVEDATE` or `START_DATE` the table has.

- **Filter on `SETTLEMENT_DAY` as well as the timestamp.** It lets the server skip whole files without reading them, and it is the main way to make a query fast. Keep the timestamp filter for the exact boundaries.
- **It is the timestamp's calendar date, not AEMO's trading day.** The interval ending 2026-10-20 00:00 has `SETTLEMENT_DAY` 20261020. When you select by trading day, make the `SETTLEMENT_DAY` range cover every timestamp you want, and let the timestamp filter make the exact cut.
- **Filter on units, constraints and regions where you can.** Each copy is also clustered on whichever of `DUID`, `CONSTRAINTID` or `REGIONID` it has, so a filter on that column lets the server skip files too.
- **Name the columns you need.** `SELECT *` returns `SETTLEMENT_DAY` as well, one more column than the same table through the MCP subscription's Databricks SQL, and reading fewer columns is quicker.
- **Aggregate in SQL**, and aggregate before joining large tables, rather than fetching rows to aggregate yourself.

For example, the average price in each region for October 2026. `SETTLEMENTDATE` marks the end of its 5-minute interval, as the guide explains, so October runs from the interval ending at 00:05 on 1 October to the one ending at 00:00 on 1 November, and `SETTLEMENT_DAY` reaches 20261101 to include that last one:

```sql
SELECT REGIONID, round(avg(RRP), 2) AS avg_price
FROM silver_dispatchis_reports_dispatch_price
WHERE SETTLEMENT_DAY BETWEEN 20261001 AND 20261101
  AND SETTLEMENTDATE > TIMESTAMP '2026-10-01 00:00' AND SETTLEMENTDATE <= TIMESTAMP '2026-11-01 00:00'
  AND INTERVENTION = 0
GROUP BY 1 ORDER BY 1
```

`EXPLAIN SELECT ...` shows the plan before you run a query, without running it.

## Limits and the trial

The trial lasts 30 days. Its allowance is measured in query time, the time your statements spend running, not in a count of queries, because one query can take a hundred times as long as another. Limits apply to the whole organisation, across all its credentials.

| Trial limit | Value |
| --- | --- |
| Length | 30 days |
| Query time | 10 hours in total |
| Statements a day | 2,000, counted on Brisbane time (AEST) and reset at midnight |
| Statements running at once | 5 |

Limits on every plan:

| Limit | Value |
| --- | --- |
| Statements per request | One `SELECT` (or `EXPLAIN` of one) |
| SQL length | 100,000 bytes |
| `wait_seconds` | 0 to 25 seconds |
| Statement run time | 300 seconds |
| Inline result | 10,000 rows and 4 MB; larger results as Parquet only |
| Result size | 5,000,000 rows |
| Parquet link | Valid for an hour |
| Statements and results | Kept for 24 hours |

A statement that fails through Renovara's fault (`INTERNAL_ERROR`) is not charged to your allowance. When a limit is reached, the error says which one and what to do; see [Errors](https://renovara.co/how-renovara-sql-works#errors). Prices are on [renovara.co/pricing](https://renovara.co/pricing).

## Examples

### Python

The Python client is `renovara-sql` on PyPI. It caches the token for its hour, polls slow statements, sends an idempotency key with every query, and returns rows, a pandas data frame or the Parquet file.

```bash
pip install renovara-sql
```

```python
from renovara_sql import Client

# What the data is: no credentials needed.
public = Client("https://sql.api.renovara.co/v1")
print(public.guide()[:2000])
for t in public.tables():
    print(t["table"], "-", t["description"][:80])

# Queries: the credentials file from the Renovara credentials page.
client = Client.from_file("~/.config/renovara-sql/acme.json")
result = client.query("""
    SELECT REGIONID, round(avg(RRP), 2) AS avg_price
    FROM silver_dispatchis_reports_dispatch_price
    WHERE SETTLEMENT_DAY BETWEEN 20261020 AND 20261027
      AND SETTLEMENTDATE > TIMESTAMP '2026-10-20 00:00' AND SETTLEMENTDATE <= TIMESTAMP '2026-10-27 00:00'
      AND INTERVENTION = 0
    GROUP BY 1 ORDER BY 1
""")
print(result.column_names, result.rows)
print(result.usage)                 # query time used, statements today
frame = result.to_pandas()          # needs pandas
result.download("prices.parquet")   # the whole result as Parquet
```

`Client.from_env()` reads `RENOVARA_SQL_API_URL`, `RENOVARA_SQL_TOKEN_URL`, `RENOVARA_SQL_CLIENT_ID` and `RENOVARA_SQL_CLIENT_SECRET` instead of a file. For control over a long query, use `submit(sql)`, `status(statement_id)` and `cancel(statement_id)`. A statement that ran and failed raises `QueryFailed`; a refused request raises `RenovaraError`. Both carry the error's `code` and `docs_url`.

### curl

```bash
# From your credentials file
API_URL=https://sql.api.renovara.co/v1/
TOKEN_URL=https://auth.api.renovara.co/oauth2/token
CLIENT_ID=...
CLIENT_SECRET=...

# 1. An access token. Keep it for its hour: not one per query.
TOKEN=$(curl -s -u "$CLIENT_ID:$CLIENT_SECRET" -d grant_type=client_credentials \
  -d scope=renovara-sql/query "$TOKEN_URL" | jq -r .access_token)

# 2. Submit a statement and wait up to 20 seconds for it.
curl -s -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"sql": "SELECT count(*) FROM silver_dispatchis_reports_dispatch_price WHERE SETTLEMENT_DAY = 20261020", "wait_seconds": 20}' \
  "${API_URL}statements"
# -> {"statement_id": "...", "state": "SUCCEEDED", "result": {"columns": ..., "rows": ...}}
#    or "state": "RUNNING": poll until it is SUCCEEDED or FAILED.

# 3. Poll a slow statement, or cancel it.
curl -s -H "Authorization: Bearer $TOKEN" "${API_URL}statements/<statement_id>"
curl -s -X POST -H "Authorization: Bearer $TOKEN" "${API_URL}statements/<statement_id>/cancel"
```

## Data source

The market data comes from the Australian Energy Market Operator (AEMO) and is used under AEMO's copyright permissions. Some reference data comes from other public sources, such as the Australian Energy Regulator; their terms continue to apply to that data. Renovara loads the data as AEMO publishes it and refreshes it daily. Where you show the data to others, credit AEMO as its source, and the Australian Energy Regulator for data that comes from it. You may pass results on to any number of your own clients, in reports, portals, AI assistants and data products; you may not hand out your credentials or resell the API itself. Use of the API is covered by the [terms of service](https://renovara.co/terms-of-service).

The data does not include the Western Australian electricity or gas markets, today's intervals before the daily refresh has loaded them, or anyone's private data, such as meter reads or settlement statements.

## Tables

The tables Renovara serves, grouped by what they cover. Query each by the name under its dataset, bare or as `external_data.nemweb.<name>`. `GET /v1/tables` returns the live list, and `GET /v1/tables/{table}` each table's columns and their meaning.

47 tables. Row counts and start dates measured 9 November 2026; the tables have grown since.

### Electricity: prices, demand and dispatch

| Table | Dataset | What it answers | Rows | From |
| --- | --- | --- | --- | --- |
| `silver_dispatchis_reports_dispatch_price` | Dispatch prices | Regional energy and frequency control (FCAS) prices for every five-minute interval. | 4.2 million | Jan 2019 |
| `silver_dispatchis_reports_dispatch_regionsum` | Regional dispatch summary | Demand, generation, interchange and frequency control requirements per region, every five minutes. | 4.2 million | Jan 2019 |
| `silver_pricesetter_price_setter` | Price setter | Which unit set each region's price in each five-minute interval. | 86.8 million | Jan 2019 |
| `silver_dispatch_scada_dispatch_unit_scada` | Unit output (SCADA) | Measured output in MW of each dispatchable unit at the start of every interval. | 347.5 million | Jan 2019 |
| `silver_next_day_dispatch_dispatch_unit_solution` | Unit dispatch targets | Each unit's dispatch target and frequency control enablement, every five minutes. | 359.1 million | Jan 2019 |
| `silver_next_day_dispatch_dispatch_offertrk` | Bid tracking | Which bid or offer each unit was dispatched against, every five minutes. | 1.2 billion | Jan 2019 |
| `silver_dispatchis_reports_dispatch_interconnectorres` | Interconnector flows | Flow, losses and limits on each interconnector, every five minutes. | 5.1 million | Jan 2019 |
| `silver_dispatchis_reports_dispatch_interconnection` | Inter-regional flows | Flows and losses between regions, every five minutes. | 736,181 | Sep 2024 |
| `silver_dispatchis_reports_dispatch_local_price` | Local price adjustments | Local pricing offsets at each unit's connection point, every five minutes. | 10.3 million | Aug 2024 |
| `silver_dispatchis_reports_dispatch_case_solution` | Dispatch run summary | One row per dispatch run, including interventions and solution status. | 229,021 | Sep 2024 |
| `silver_operational_demand_actual_operational_demand_actual` | Operational demand | Measured operational demand per region, every half hour. | 199,245 | Aug 2024 |
| `silver_rooftop_pv_actual_rooftop_actual` | Rooftop solar (estimated actual) | AEMO's estimate of rooftop solar output per region, every half hour. | 2.7 million | Jan 2019 |

### Electricity: forecasts

| Table | Dataset | What it answers | Rows | From |
| --- | --- | --- | --- | --- |
| `silver_rooftop_pv_forecast_rooftop_forecast` | Rooftop solar forecast | AEMO's half-hourly rooftop solar forecast per region, up to eight days ahead. | 253.0 million | Jan 2019 |
| `silver_p5_reports_p5min_regionsolution` | Five-minute predispatch, regions | Projected regional prices and demand for the next hour, in five-minute steps. | 6.1 million | Nov 2025 |
| `silver_p5_reports_p5min_interconnectorsoln` | Five-minute predispatch, interconnectors | Projected interconnector flows for the next hour. | 7.5 million | Nov 2025 |
| `silver_p5_reports_p5min_local_price` | Five-minute predispatch, local prices | Projected local price adjustments for the next hour. | 62.9 million | Nov 2025 |
| `silver_predispatchis_reports_predispatch_region_prices` | Predispatch prices | Projected regional prices by half hour from each predispatch run. | 4.7 million | Nov 2025 |
| `silver_predispatchis_reports_predispatch_region_solution` | Predispatch regional solution | Projected regional demand, generation and reserves from each predispatch run. | 4.7 million | Nov 2025 |
| `silver_predispatchis_reports_predispatch_interconnector_soln` | Predispatch interconnectors | Projected interconnector flows and losses from each predispatch run. | 5.8 million | Nov 2025 |
| `silver_predispatchis_reports_predispatch_local_price` | Predispatch local prices | Projected local price adjustments from each predispatch run. | 46.0 million | Nov 2025 |

### Electricity: constraints and losses

| Table | Dataset | What it answers | Rows | From |
| --- | --- | --- | --- | --- |
| `silver_dispatchis_reports_dispatch_constraint` | Binding constraints | Binding and inter-regional constraints in each dispatch run, with their marginal values. | 218.4 million | Sep 2024 |
| `silver___gencondata` | Constraint catalogue | The generic constraints AEMO uses, with their descriptions. | 98,570 | Jan 2025 |
| `silver___genconset` | Constraint sets | Which constraints belong to each constraint set. | 149,171 | Jan 2025 |
| `silver___spdcpc` | Constraint factors, connection points | The factor each constraint applies to each connection point. | 2.4 million | Jan 2025 |
| `silver___spdrc` | Constraint factors, regions | The factor each constraint applies to regional generation and load. | 6,051 | Jan 2025 |
| `silver_marginal_loss_factors_daily_mlf` | Marginal loss factors | Marginal loss factors by connection point, by effective date and version. | 1.0 million | Jul 2011 |

### Electricity: registration and reference

| Table | Dataset | What it answers | Rows | From |
| --- | --- | --- | --- | --- |
| `silver___participant_registration_dudetailsummary` | Unit details | Each dispatchable unit's region, connection point, type and loss factors over time. | 433,915 | Jun 2007 |
| `silver___participant_registration_dualloc` | Units to gensets | Which gensets make up each dispatchable unit, over time. | 27,526 | Oct 1998 |
| `silver___participant_registration_genunits` | Gensets | Every genset AEMO has registered, retired units included. | 8,759 | Nov 2005 |
| `silver___participant_registration_participant` | Market participants | Participant IDs, names and classes. | 24,074 | Jun 1998 |
| `silver___participant_registration_dudetail` | Unit capacity history | Each unit's registered and maximum capacity and storage over time. | 40,055 | Oct 1998 |
| `silver___participant_registration_station` | Stations | Every station AEMO has registered, with its name and address. | 5,823 | Nov 2003 |
| `v_duid_registration` | Unit register | One current row per unit, with its region, station, participant, type, fuel and capacity. | 1,019 | Current list |
| `v_duid_fuel_aemo` | Unit fuel (AEMO) | Fuel type for each unit, in AEMO's classification. | 1,020 | Current list |
| `v_duid_fuel_aer` | Unit fuel (AER) | Fuel type for each unit, in the Australian Energy Regulator's classification. | 1,019 | Current list |

### Gas: AEMO Gas Bulletin Board

| Table | Dataset | What it answers | Rows | From |
| --- | --- | --- | --- | --- |
| `silver_gbb_actual_flow_storage` | Actual flows and storage | Daily production, flows and storage by facility. | 453,543 | Daily |
| `silver_gbb_pipeline_connection_flow` | Pipeline connection flows | Daily flows at pipeline connection points. | 1.7 million | Daily |
| `silver_gbb_nomination_forecast` | Nominations and forecasts | Facility nominations and forecasts for coming gas days. | 9,355 | Daily |
| `silver_gbb_short_term_capacity_outlook` | Short-term capacity outlook | Facility capacity for the coming days. | 12,028 | Daily |
| `silver_gbb_medium_term_capacity_outlook` | Medium-term capacity outlook | Planned capacity changes over the coming months. | 34,826 | Daily |
| `silver_gbb_linepack_capacity_adequacy` | Linepack capacity adequacy | Daily linepack and capacity adequacy status by pipeline. | 3,834 | Daily |
| `silver_gbb_linepack_zones` | Linepack zones | The pipeline segments linepack is reported for. | 56 | Daily |
| `silver_gbb_facilities` | Facilities | The Bulletin Board's register of facilities. | 159 | Daily |
| `silver_gbb_participants` | Participants | Bulletin Board participants. | 260 | Daily |
| `silver_gbb_basins` | Basins | Gas basins. | 15 | Daily |
| `silver_gbb_locations` | Locations | Bulletin Board locations. | 25 | Daily |
| `silver_gbb_nodes_connection_points` | Nodes and connection points | Pipeline nodes and connection points. | 775 | Daily |

## Errors

Every error carries a stable `code`, a `message` that says what to change, and a `docs_url` that links to the code's entry below. Agents can correct themselves from them.

A refused request returns an HTTP error status with the error as its body:

```json
{"error": {"code": "DAILY_LIMIT", "message": "...", "docs_url": "https://renovara.co/how-renovara-sql-works#error-daily-limit"}}
```

A statement that ran and failed returns HTTP 200 with `"state": "FAILED"` and the same object in the statement's `error` field. `DAILY_LIMIT` and `TOO_MANY_RUNNING` responses carry a `Retry-After` header with the seconds to wait.

### Refused requests

#### `BAD_REQUEST`

**HTTP 400.** The request is malformed: send JSON {"sql": "SELECT ..."}.

#### `UNAUTHORIZED`

**HTTP 401.** No valid access token. Exchange your client id and secret for one at the token URL (OAuth client credentials) and send it as a Bearer token.

A token lasts 60 minutes. Fetch a new one when it expires, and reuse it until then. See [Authentication](https://renovara.co/how-renovara-sql-works#authentication).

#### `CLIENT_NOT_REGISTERED`

**HTTP 403.** The credentials are valid but not registered to an organisation. Create credentials on the Renovara credentials page.

#### `CLIENT_REVOKED`

**HTTP 403.** These credentials were revoked. Create new ones.

#### `SUBSCRIPTION_PAUSED`

**HTTP 403.** The organisation's subscription is paused. Add a payment method in the Renovara customer portal to resume.

#### `SUBSCRIPTION_ENDED`

**HTTP 403.** The organisation's subscription has ended.

#### `TRIAL_ENDED`

**HTTP 403.** The 30-day trial has ended. Subscribe to continue.

Prices are on [renovara.co/pricing](https://renovara.co/pricing).

#### `ALLOWANCE_USED`

**HTTP 403.** The plan's query-time allowance is used up. Subscribe to continue.

The trial's allowance is 10 hours of query time in total. See [Limits and the trial](https://renovara.co/how-renovara-sql-works#limits-and-the-trial).

#### `NOT_FOUND`

**HTTP 404.** No such statement for this client (statements last 24 hours), table, reference or route.

A statement is visible only to the client that submitted it, and lasts 24 hours.

#### `DAILY_LIMIT`

**HTTP 429.** The organisation's statements for today (Brisbane time) are used up. Retry-After says when the count resets.

The trial allows 2,000 statements a day for the whole organisation.

#### `TOO_MANY_RUNNING`

**HTTP 429.** The organisation has its maximum statements running. Wait for one to finish, or cancel one; Retry-After suggests when to try.

The trial allows 5 statements running at once for the whole organisation.

#### `THROTTLED`

**HTTP 429.** Too many requests a second. Back off and retry.

### Failed statements

#### `QUERY_REJECTED`

**Statement state FAILED (HTTP 200).** not one read-only SELECT, or it calls a function that reports the server's configuration. Rewrite and resubmit.

`EXPLAIN SELECT ...` and `EXPLAIN ANALYZE SELECT ...` are accepted as well as `SELECT`.

#### `SQL_ERROR`

**Statement state FAILED (HTTP 200).** DuckDB could not run the SQL. The message is DuckDB's; the API runs DuckDB, not Databricks SQL.

The most common cause is Databricks syntax. See [DuckDB SQL](https://renovara.co/how-renovara-sql-works#duckdb-sql) for the translations.

#### `QUERY_TIMEOUT`

**Statement state FAILED (HTTP 200).** it ran past 300 seconds. Narrow the time range or the units, or aggregate before joining.

Filtering on `SETTLEMENT_DAY` as well as the timestamp is the main way to make a query faster. See [Writing fast queries](https://renovara.co/how-renovara-sql-works#writing-fast-queries).

#### `RESULT_TOO_LARGE`

**Statement state FAILED (HTTP 200).** over 5,000,000 rows. Aggregate or page.

#### `CANCELLED`

**Statement state FAILED (HTTP 200).** it was cancelled.

#### `TIMED_OUT`

**Statement state FAILED (HTTP 200).** the worker stopped without recording a result.

Resubmit the statement. If it happens again, email [info@renovara.co](mailto:info@renovara.co) with the statement ID.

#### `INTERNAL_ERROR`

**Statement state FAILED (HTTP 200).** Renovara's failure, not the query's. Retry; it is not charged to your allowance.

If it happens again, email [info@renovara.co](mailto:info@renovara.co) with the statement ID.

## Try it with your own code.

A 30-day free trial with 10 hours of query time and no card. The guide and the table schemas are open to read before you sign up.

- [Start SQL API trial](https://sql.api.renovara.co/v1/trial/checkout)
- [About the SQL API](https://renovara.co/sql-api)

Already started a trial? [Manage your API credentials](https://renovara.co/sql-api-credentials)
