Skip to contents

Data Receipt collects validated spreadsheets from the people you ask: you define the columns and rules, share a link, and every upload has to pass before it is accepted. This package pulls what they sent into R as tidy, typed tibbles, so the data you asked for lands ready to use.

If you have used tidycensus, it works the same way: set a key once, look up what is available, then get_ the data.

Installation

The package is not on CRAN yet. Install it from GitHub:

# install.packages("pak")
pak::pak("dgkeyes/datareceipt")

Set up

Create an API token on the site under Settings > API tokens, then save it once:

library(datareceipt)

datareceipt_api_key("12|dr_...", install = TRUE)

That writes DATARECEIPT_API_KEY to your .Renviron, so every future session finds it. Check it worked with datareceipt_whoami().

Get your data

# What have I asked for?
list_requests()

# What columns did request 12 ask for, and with what rules?
get_columns(12)

# Everything anyone has sent to request 12, as one tibble
hours <- get_data(12)

You can pass a request’s URL straight from your browser instead of its id:

hours <- get_data("https://datareceipt.io/requests/12")

get_data() returns one row per submitted row. Columns are typed from the request’s spec (text is character, numbers are double, whole numbers are integer, dates are Date, yes/no is logical), and the first few columns say where each row came from:

#> Fetched 1,204 rows from 3 submissions for request 12.
#> # A tibble: 1,204 × 8
#>   submission_id   row submitted_at        sender_name sender_email  site   month      hours
#>           <int> <int> <dttm>              <chr>       <chr>         <chr>  <date>     <dbl>
#> 1            41     1 2026-08-13 22:20:49 Vivian Lee  vivian@ex.org North  2026-07-01  12.5

Pass include_sender = FALSE to leave the sender columns out. list_submissions() and get_submission() work one submission at a time.

Flagged cells

A column can be set to accept a value that breaks its rules and flag it, rather than block the submission. Those cells come back from get_data() cast where the text allows (an out-of-range "200" in a whole-number column is 200L) and as NA where it does not ("abc" in the same column). get_flags() lists them all:

get_flags(12)
#> # A tibble: 2 × 8
#>   submission_id   row submitted_at        sender_name sender_email  column value message
#>           <int> <int> <dttm>              <chr>       <chr>         <chr>  <chr> <chr>
#> 1            41     7 2026-08-13 22:20:49 Vivian Lee  vivian@ex.org hours  -3    must be at least 0