Thanks for your interest in improving oddsapiR, the SportsDataverse R client for The Odds API! This guide covers how to set up a development environment, the conventions the package follows, and how to get a change merged.
By participating you agree to abide by the Code of Conduct.
Ways to contribute
- Report a bug – open an issue using the Bug Report template.
- Request a feature / new endpoint – open an issue using the Feature Request template.
-
Improve docs – typo fixes, clearer examples, and better
@returntables are always welcome. - Add or fix a wrapper – see the conventions below.
Getting set up
Fork and clone the repository.
-
Install the development dependencies (pak recommended; devtools also works):
# recommended: install.packages("pak") pak::local_install_dev_deps() # or with devtools: install.packages("devtools") devtools::install_dev_deps() -
Load the package for interactive development:
devtools::load_all()
The Odds API key
Most functions hit the live API, so you need a (free) key from https://the-odds-api.com. Store it as the ODDS_API_KEY environment variable:
usethis::edit_r_environ()
# add the line: ODDS_API_KEY=your-key-here
# then restart RConfirm it is picked up with has_toa_key(). Never commit your key. Historical (/v4/historical/...) endpoints require a paid plan; tests for those skip cleanly when the plan tier returns no data.
Be mindful of your usage quota – toa_quota() reports the credits used/remaining for the most recent call, and the same numbers print with every returned tibble.
Development workflow
devtools::load_all() # load the package
devtools::document() # regenerate man/ + NAMESPACE after changing roxygen
devtools::test() # run the test suite (needs ODDS_API_KEY)
devtools::build_readme() # re-render README.md from README.Rmd
devtools::check() # full R CMD check before opening a PRCoding conventions
oddsapiR follows the conventions documented in CLAUDE.md. The essentials:
-
Naming. Public functions are prefixed
toa_and mirror the endpoint shape. -
HTTP layer. Never call
httr2directly. Build a base URL + a namedquery_paramslist and calltoa_api_call(base_url, query = query_params).NULLquery values are dropped automatically, so thread optional parameters through unconditionally. -
Initialize the return variable (e.g.
odds <- data.frame()) before thetryCatchso an API error returns an empty tibble instead of erroring on an unbound name. -
Parse to a tidy tibble with
tidyr::unnest()overbookmakers -> markets -> outcomes, renaming the ambiguouskey/last_updatefields at each level, then finish withmake_toa_data(<description>, Sys.time()). -
Self-describing returns. Echo identity columns the API omits (e.g.
sport_keyon participants; snapshot timestamps on historical wrappers). -
@returntables are 3-column:col_name | types | description. Include a Usage quota cost note in the@description. -
Messaging uses
cli::cli_alert_*, not baremessage().
Testing
Add a
tests/testthat/test-<function>.Rfile for every new function.Gate live tests:
skip_on_cran()andskip_if_not(has_toa_key(), "ODDS_API_KEY not set").Guard against empty/transient responses before asserting columns:
if (!is.data.frame(x) || nrow(x) == 0) skip("No rows returned at test time").-
Use subset-direction assertions so upstream column additions never break the suite:
Documentation
- Never hand-edit
NAMESPACEor anything underman/– regenerate withdevtools::document(). - Re-render the README with
devtools::build_readme()and commitREADME.Rmd+README.mdtogether. - Add new exported functions to the
reference:section of_pkgdown.yml. - For user-visible changes, update
NEWS.mdandcran-comments.md.
Commit & PR conventions
- Use Conventional Commits:
feat(toa):,fix(toa):,docs:,test(toa):,refactor:,chore:,ci:. Mark breaking changes withtype!:or aBREAKING CHANGE:footer. - Keep unrelated changes in separate commits.
- Do not add AI tools (Claude, Copilot, etc.) as commit co-authors.
- Open your PR against
main, fill out the PR template, and confirmdevtools::check()anddevtools::test()pass.
Thanks again for contributing!
