mc.io lets you point the SDK at a spreadsheet, run the Data API on its
rows, and write the results back — without the manual “read CSV → build
records → call API → flatten response” loop.
Two formats today: Google Sheets (via gspread) and Excel .xlsx
(via openpyxl). Each is gated on its own optional extra so the base wheel
stays small.
pip install "minerva-sdk[gsheet]" # Google Sheets I/O
pip install "minerva-sdk[excel]" # .xlsx I/O
Google Sheets
Read + run + write
from minerva import Minerva
mc = Minerva() # MINERVA_API_KEY from env
# SHEET_ID is the long string between `/d/` and `/edit` in your spreadsheet's URL
SHEET_ID = "<paste-your-google-sheet-id-here>"
CREDS = "/path/to/service-account.json" # or a gspread.Client / dict / google Credentials
resp = mc.io.enrich_from_sheet(
SHEET_ID,
credentials=CREDS,
sheet_name="Customers",
match_condition_fields=["linkedin_url"],
)
resp.to_sheet(
SHEET_ID,
credentials=CREDS,
sheet_name="Enriched",
)
resolve_from_sheet works the same way and returns a ResolveResponse.
Credentials
The credentials= kwarg accepts whatever’s most natural for your
environment — the SDK figures out what shape you passed:
| You have | Pass |
|---|
| A service-account JSON file | credentials="/path/to/sa.json" |
| Parsed service-account dict | credentials={...} |
A built google.oauth2 Credentials | credentials=creds |
A built gspread.Client | credentials=gc |
GOOGLE_APPLICATION_CREDENTIALS env var | omit credentials= entirely |
The env-var fallback matches Google’s own conventions, so apps already
running with that variable set need no extra config.
Default scopes are read-only for enrich_from_sheet / resolve_from_sheet
/ read_sheet, and read-write for write_sheet / to_sheet. If you pass
a pre-built gspread.Client or Credentials, the SDK uses the scopes you
configured — bring your own if you need something different.
Field mapping
By default the first row is the header, and each column name maps
1:1 to an enrich field (record_id, linkedin_url, first_name,
emails, …). If your sheet has different column names, rename via
field_mapping:
mc.io.enrich_from_sheet(
sheet_id,
credentials=...,
field_mapping={
"customer_id": "record_id",
"li": "linkedin_url",
},
)
Columns not in the mapping pass through unchanged. Empty cells are dropped
so pydantic-Optional fields stay None.
Range and tab
mc.io.enrich_from_sheet(sheet_id, credentials=..., sheet_name="Q2 Customers")
mc.io.enrich_from_sheet(sheet_id, credentials=..., range_="A1:F500")
Omit sheet_name to use the first tab. Omit range_ to read the whole
worksheet.
Chunking
The Data API caps enrich and resolve at 500 records per call. mc.io
chunks transparently — pass a 2,000-row sheet, the SDK runs four batches
and returns one merged EnrichResponse (the api_request_id field
reflects the last batch).
Lower-level read/write
If you need just the rows (not the API call), use the raw helpers:
rows = mc.io.read_sheet(sheet_id, credentials=..., sheet_name="Tab")
# -> [["record_id", "linkedin_url"], ["1", "https://lnkd.in/a"], …]
mc.io.write_sheet(
sheet_id,
[["col_a", "col_b"], [1, 2], [3, 4]],
credentials=...,
sheet_name="Tab",
range_="A1", # starting cell
mode="overwrite", # or "append"
)
Excel (.xlsx)
Same shape as Sheets, but reads from a local file path — no auth required.
resp = mc.io.enrich_from_excel(
"customers.xlsx",
sheet_name="Sheet1",
match_condition_fields=["linkedin_url"],
)
resp.to_excel("enriched.xlsx")
If the workbook has more than one sheet, sheet_name= is required (we
don’t pick for you):
mc.io.enrich_from_excel("workbook.xlsx", sheet_name="Q2 2026")
Raw row access works the same way:
rows = mc.io.read_excel("customers.xlsx")
mc.io.write_excel([["a", "b"], [1, 2]], "out.xlsx", sheet_name="Results")
Writing any response to a sheet
to_sheet() and to_excel() live on every typed response — EnrichResponse,
ResolveResponse, UsageResponse, etc. The response is flattened the same
way to_csv() flattens it (union of keys across all rows, missing cells
left blank):
mc.api.enrich(records).to_sheet(sheet_id, credentials=...)
mc.usage.tallies(start="2026-01-01", end="2026-06-01").to_excel("usage.xlsx")
Errors
| Condition | Raises |
|---|
Extra not installed ([gsheet] / [excel] missing) | MinervaValidationError("install minerva-sdk[<extra>]") |
| Bad credentials, 401 / 403 from Google | MinervaAuthError |
| Spreadsheet not found (404) | MinervaAPIError(status_code=404) |
| Tab not found, malformed sheet, empty rows | MinervaValidationError |
| Wrong file extension (Excel) | MinervaValidationError |
| API errors from the underlying enrich/resolve call | Their usual exceptions (MinervaRateLimitError, etc.) |
Setting up a Google service account (one-time)
If you don’t have one yet:
- Create a project in Google Cloud Console.
- Enable the Google Sheets API for that project.
- Create a service account, download its JSON key.
- Share the target sheet with the service account’s email address
(it looks like
name@project.iam.gserviceaccount.com). Without this
step the service account can’t see the sheet, even with valid creds.
- Point
credentials= at the JSON path, or set
GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa.json in your environment.
Service accounts are usually the right call for server-side / scheduled
runs. For interactive / notebook use where you’d prefer to log in as
yourself, build a gspread.Client with OAuth and pass it to
credentials=.