# contractorOps.import.agreements

Bring agreements and NDAs in from a contracts table such as the Notion Contractor Contracts database, or the NDAs from a documents table such as Contractors Documents (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; the rest are matched by name. Fields: contractor (or email), title, kind (a type column: NDA, Contract…), signed (a signed column), role, rate (hourly), currency, startDate, endDate, file (a Notion files column with the signed copy), fileUrl (a link to a copy kept elsewhere) and nda (an NDA link or file on a contract row). A signed agreement with a start date becomes a version of the contractor's terms from that date, unless terms starting that day are already on file; one without dates is recorded as signed with the dates not stated and never becomes terms, so it can't replace dated ones; an unsigned, draft or stale row is kept as an unsigned draft. The signed copy is kept: a file attached to a Notion row is brought in with the Notion file import and filed as the agreement's signed document (linked to its terms), and a copy that's only a link is recorded as signed elsewhere. NDAs are kept apart from the contractor agreement. The role is linked to a job role by title and, for a title with several levels, the level whose pay band holds the rate; otherwise jobRoleSuggestion says what to do, and a contractor with no job role gets the one their current agreement matches. A table without start dates only brings in its NDAs. Every row is kept by the row it came from, so importing again fills in what's missing (a signed copy, a job role) instead of adding it twice. Rows for someone not in Oatmilk are left out with the reason; a rate of $1 or less is read as none. Nothing is sent to sign and nobody is emailed. 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 each row says whether it's already on file (onFile) and where the table disagrees with the terms on file (conflict).

`POST /api/v1/accounting/contractorOps.import.agreements`

Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required

MCP tool: `accounting_contractor_ops_import_agreements`

## Fields

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `source` | enum | Yes | notion reads a Notion database on the server; csv takes the rows of a parsed CSV file. One of: `csv`, `notion`. |
| `rows` | array of maps |  | For csv: each row as column name to value, at most 500. 1–500 items. |
| `databaseId` | string |  | For notion: the database's link or ID. The database must be shared with the Oatmilk connection. 1–2000 characters. |
| `mapping` | object |  | The 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. |
| `mapping.contractor` | string or null |  | The column holding Contractor (required), or null when the table has none. |
| `mapping.email` | string or null |  | The column holding Email, or null when the table has none. |
| `mapping.title` | string or null |  | The column holding Agreement, or null when the table has none. |
| `mapping.kind` | string or null |  | The column holding Type, or null when the table has none. |
| `mapping.signed` | string or null |  | The column holding Signed, or null when the table has none. |
| `mapping.role` | string or null |  | The column holding Role, or null when the table has none. |
| `mapping.rate` | string or null |  | The column holding Rate, or null when the table has none. |
| `mapping.rateUnit` | string or null |  | The column holding Paid per, or null when the table has none. |
| `mapping.currency` | string or null |  | The column holding Currency, or null when the table has none. |
| `mapping.startDate` | string or null |  | The column holding Start date, or null when the table has none. |
| `mapping.endDate` | string or null |  | The column holding End date, or null when the table has none. |
| `mapping.file` | string or null |  | The column holding Signed copy (file), or null when the table has none. |
| `mapping.fileUrl` | string or null |  | The column holding Signed copy (link), or null when the table has none. |
| `mapping.nda` | string or null |  | The column holding NDA, or null when the table has none. |
| `preview` | boolean |  | true checks every row and saves nothing. |
| `idempotencyKey` | string | Yes | Any 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 request

```bash
curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.import.agreements \
  -H "Authorization: Bearer $OATMILK_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "source": "csv",
  "rows": [
    {}
  ]
}'
```

Reference page: https://app.getoatmilk.com/docs/api/contractorOps.import.agreements
