contractorOps.import.hours
Bring past timesheets in as history from a table (source notion with databaseId, the database's link or ID, or csv with rows; mapping names the column for any fields whose column names don't match, and the others are matched by name). Fields: contractor (or firstName and lastName, or email), period (a date or a range, with optional periodEnd), hours, and optional rate, amount, tax, currency, paid, notes and submitted. Rows are matched to contractors by email, then name. Imported hours appear in each contractor's history, spending and on-time insights, but never become pay periods or payouts. A row that is a timesheet already here (the same Notion page or CSV row, or the same contractor's for the same hours starting or ending the same day) updates it instead of adding a duplicate: edited dates, hours, rate, amount, GST/HST and notes are taken, a blank cell erases nothing, and Paid marks an unpaid timesheet paid but an unpaid row never marks a paid one unpaid. The result lists what was added, corrected and marked paid in saved (counts and rows) and in summaryText, and afterwards new unpaid timesheets are settled against bank payments already linked to the contractor (payments). A column whose name says it holds payment or tax details is never read, and problems says so when mapping names one. With preview true nothing is saved and changes says what would happen.
/api/v1/accounting/contractorOps.import.hoursPermissions
Who can call it
Retries
MCP
accounting_contractor_ops_import_hoursFields
sourceenumRequirednotion reads a Notion database on the server; csv takes the rows of a parsed CSV file.
csvnotionrowsarray of mapsFor csv: each row as column name to value, at most 500.
1–500 items
databaseIdstringFor notion: the database's link or ID. The database must be shared with the Oatmilk connection.
1–2000 characters
mappingobjectThe column for any fields whose column names don't match (null for none). Fields left out, or every field when it's omitted, are matched by column name; the result returns the mapping it used, so a preview shows the suggested one.
previewbooleantrue checks every row and saves nothing.
idempotencyKeystringRequiredAny unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match.
8–200 characters
Example
curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.import.hours \
-H "Authorization: Bearer $OATMILK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"source": "csv",
"rows": [
{}
]
}'{
"data": { … }
}Try it
Try it
Checks your input with this action’s real schema and answers like the API, with synthetic data. No key needed, and nothing changes.
curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.import.hours \
-H "Authorization: Bearer $OATMILK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"source": "csv",
"rows": [
{}
]
}'More in Contractors.