dlt-source-aquabyte
A dlt source for the Aquabyte API v3: sites, biomass, lice counts, welfare scores, behaviour and environmental readings, into any dlt destination.
Records land as the API returns them — nothing renamed, nothing dropped, no invented child tables. Column names are the API's own, in dlt's usual snake_case. Reshaping belongs in your transform layer, where you can change it without waiting for a release.
The package handles auth, pagination, envelope unwrapping, incremental cursors and window splitting. Its only dependency is dlt.
How to start
uv add dlt-source-aquabyte "dlt[duckdb]" # any dlt destination works; DuckDB is the one below
Then two files in a .dlt/ directory beside your script.
.dlt/config.toml:
[sources.aquabyte]
base_url = "https://api.aquabyte.ai/v3/"
initial_date = "2020-01-01" # first-run start, date resources
initial_time = "2020-01-01T00:00:00Z" # first-run start, time resources
.dlt/secrets.toml:
[sources.aquabyte]
api_key = "your-api-key-here"
For the two start values, choose a date that predates when you first lowered an Aquabyte camera into the sea. A start earlier than your first record costs empty requests, not errors, and step 2 below replaces the guess with measurements.
Then four steps, one example each. That is everything needed to get running, a page of code each.
- Discover —
discover_history.py. How far back your account goes, how much is there, and how fresh each resource is. Its output decides steps 2 and 3. - Configure again, now from measurements.
periodandbucket_sizeper resource for the granularity you want (Configuring a resource). And moveinitial_date/initial_timeforward to the day step 3 will end, because they are where the daily load starts: a backfill leaves no cursor behind, so a daily load still pointed at the old date would re-request every year you are about to load. - Backfill —
backfill.py. Put the earliest dates from step 1 at the top of the file and run it once. The stored cursor is left alone, so this can be re-run at any time without disturbing step 4. - Daily load —
daily_load.py. The same script on a timer from then on, each run resuming from the cursor.
No step computes a window: the source splits a multi-year span into requests the API accepts. From a checkout, run one with python examples/<name>.py.
Two resources need more than the start values above:
-
welfare_scoresrefuses any start before 2024-04-20, so an olderinitial_datefails that one resource on every run. Give it a start of its own — bothdiscover_history.pyandbackfill.pyalready do:source.welfare_scores.bind(incremental_date=dlt.sources.incremental(initial_value="2024-04-20"))
-
sitesandenvironmental_latestneed neither value, keeping no cursor. A cursor resource missing one fails with an error naming it.
What it loads
| Resource | Endpoint | Load strategy | Key |
|---|---|---|---|
sites |
GET /sites, GET /sites/{siteId} |
merge, scd2 |
id (merge key) |
environmental |
GET /environmental |
merge | penId, fromTime, toTime |
environmental_latest |
GET /environmental/latest |
replace | — |
biomass |
GET /biomass |
merge | penId, date |
harvest_report |
GET /biomass/harvestReport |
merge | penId, slaughterStartDate, mainReport, asOfDate |
lice_count |
GET /liceCount |
merge | penId, date |
behaviour_swim_speed |
GET /behaviour/swimSpeed |
merge | penId, fromTime, toTime |
behaviour_breathing_index |
GET /behaviour/breathingIndex |
merge | penId, fromTime |
welfare_scores |
GET /welfareScores |
merge | penId, date |
Three defaults worth knowing before your first query:
- There is no pens table. The API serves no pens endpoint, so each site record carries its pens as the API nests them, active or not — where pen history lives.
sitesis versioned, not replaced. A pen leaves/sitesas soon as it is emptied, so a row is retired rather than deleted — what that means for your queries.- Nested objects land as one JSON column each,
welfare_scoresincluded — why, and how to override it.
Configuring a resource
Each resource takes its endpoint's params in snake_case. The window is the exception: the incremental cursor drives it, and a backfill binds it on the resource's incremental_* argument (how).
source = aquabyte_source()
source.sites.bind(site_id="site-001") # switches to GET /sites/{siteId}
source.biomass.bind(pen_id="pen-abc", bucket_size=250)
pipeline.run(source)
| Param | What to know |
|---|---|
pen_id |
Defaults to "all", the API's own value for every pen in one request. Pass one id to read a single pen. |
site_id |
The one path param. Binding it moves sites to the per-site endpoint; both write the same table. |
params |
On every resource, merged into the query string last, so it wins over every named param. The escape hatch for a param the API grows later. |
max_window_days |
On every resource with a cursor. The widest window one request asks for, in days. Leave it out and the package's own window cap applies (reference). |
Params can also be set in config, per resource:
[sources.aquabyte.environmental]
period = "15min"
Two params decide the granularity of the data itself
period and bucket_size change what the API computes for you, not which rows you ask for. Decide both before the first load: a coarse setting is not wrong, but the detail under it never lands, and getting it later means re-loading that history the backfill way.
| Param | Resource | Values | API default | What it decides |
|---|---|---|---|---|
period |
environmental |
h, D, 15min |
D |
Row granularity: h is 24× the rows of D, 15min is 96× |
period |
behaviour_swim_speed |
h, D |
D |
As above. 15min here is a 422 — only environmental takes it |
bucket_size |
biomass |
integer grams | 1000 |
Bucket width of the nested weightDist histogram — no extra rows |
⚠️ Changing period later leaves both granularities in the table. The key is penId + fromTime + toTime, so hourly rows do not merge over the daily ones they cover. Keep one period per resource, or re-load the history behind the change. That is also why behaviour_breathing_index drops toTime from its key: it is daily-only, so its granularity cannot change.
period also sets the widest window the API accepts — 7 days at 15min, 31 at h, 366 at D — and the source splits its requests to fit, so a long catch-up works at any granularity (detail).
weightDist covers only the weights observed, so a smolt pen returns two buckets and a harvest-size pen at 250 g a few dozen (what the arrays hold).
Compatibility
dlt-source-aquabyte |
Aquabyte API |
|---|---|
| 0.1.x | v3.1 |
The two numbers are unrelated: the package version is ordinary SemVer. Built against that API version's specs/openapi.json and run live against it, last on 2026-08-31. A later backwards-compatible API version should work, but run the suite first.
Read next
- API quirks worth knowing — where the live API departs from its OpenAPI document, and which identifiers to join on. Some change what a correct query looks like, so read it before your first one.
- Reference — site versioning, nesting, backfilling, window splitting, logging and column types.
- Changelog and contributing.
License
Apache-2.0. specs/openapi.json is Aquabyte's own OpenAPI document, included as the spec this package is built against. It is their material, and the licence does not extend to it.
Metadata
Release files for dlt-source-aquabyte 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dlt_source_aquabyte-0.4.0.tar.gz | 37.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dlt_source_aquabyte-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.3 kB
Release files / dlt_source_aquabyte-0.4.0.tar.gz
| Download URL | dlt_source_aquabyte-0.4.0.tar.gz |
|---|---|
| Size | 37.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a0eee73a5662dfcd7d42b6bb95128d69291ff7cb918fb00fbb7946f791e971c8
|
|
BLAKE2b-256 checksum How to use checksums |
ca88bd708e0aa52b49fa6eec8166936e45f553eedcd225fb08960db7ee89c533
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / dlt_source_aquabyte-0.4.0-py3-none-any.whl
| Download URL | dlt_source_aquabyte-0.4.0-py3-none-any.whl |
|---|---|
| Size | 16.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
865653c25a44888968f2c31c28102acde0ccef373ecc349275299db99605bb0c
|
|
BLAKE2b-256 checksum How to use checksums |
cf6707246ecd203e767a23bed0eef06e0439eb9fd2c84366bc64cabbe2d81031
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency log