Skip to main content
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 havePass
A service-account JSON filecredentials="/path/to/sa.json"
Parsed service-account dictcredentials={...}
A built google.oauth2 Credentialscredentials=creds
A built gspread.Clientcredentials=gc
GOOGLE_APPLICATION_CREDENTIALS env varomit 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

ConditionRaises
Extra not installed ([gsheet] / [excel] missing)MinervaValidationError("install minerva-sdk[<extra>]")
Bad credentials, 401 / 403 from GoogleMinervaAuthError
Spreadsheet not found (404)MinervaAPIError(status_code=404)
Tab not found, malformed sheet, empty rowsMinervaValidationError
Wrong file extension (Excel)MinervaValidationError
API errors from the underlying enrich/resolve callTheir usual exceptions (MinervaRateLimitError, etc.)

Setting up a Google service account (one-time)

If you don’t have one yet:
  1. Create a project in Google Cloud Console.
  2. Enable the Google Sheets API for that project.
  3. Create a service account, download its JSON key.
  4. 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.
  5. 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=.