> For the complete documentation index, see [llms.txt](https://docs.ninox.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ninox.com/ninox-api/api-reference/api-endpoints/records.md).

# Records

## Get records from a table

> Retrieves paginated rows from a table with optional filtering and sorting

```json
{"openapi":"3.0.0","info":{"title":"Ninox Public API","version":"1.0.0"},"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"API Key","description":"Enter your Workspace API key. \n\n API keys can be generated and managed within the **Workspace Integration** settings in the Ninox app.","in":"header","name":"Authorization","type":"http"}},"schemas":{"RowsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"values":{"type":"object","additionalProperties":{"anyOf":[{"type":"boolean"},{"type":"number"},{"type":"string"}],"nullable":true}}},"required":["id","values"]},"description":"List of rows"},"meta":{"type":"object","properties":{"filter":{"type":"object","additionalProperties":{"anyOf":[{"type":"boolean"},{"type":"number"},{"type":"string"}],"nullable":true},"description":"Applied filter conditions"},"sort":{"type":"string","description":"Applied sort field"}}},"page_info":{"type":"object","properties":{"has_more":{"type":"boolean","description":"Whether more results are available"},"limit":{"type":"number","description":"Maximum results per page"},"offset":{"type":"number","description":"Current offset"}},"description":"Pagination information"}},"required":["data","page_info"]},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"Error message"}}}},"required":["error"]}}},"paths":{"/api/v1/workspace/{workspaceId}/modules/{moduleName}/tables/{tableName}/records":{"get":{"operationId":"RowsV1Controller_getRows","summary":"Get records from a table","description":"Retrieves paginated rows from a table with optional filtering and sorting","parameters":[{"name":"moduleName","required":true,"in":"path","description":"Module name","schema":{"minLength":1,"type":"string"}},{"name":"tableName","required":true,"in":"path","description":"Table name","schema":{"minLength":1,"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"minLength":12,"maxLength":12,"pattern":"^[a-z0-9]+$","type":"string"}},{"name":"fields","required":false,"in":"query","description":"Comma-separated list of field names to include","schema":{"type":"string"}},{"name":"filter","required":false,"in":"query","description":"Filter conditions as a JSON string (e.g., {\"email\": \"test@example.com\"})","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of rows to return (1-100)","schema":{"minimum":1,"maximum":100,"type":"number"}},{"name":"offset","required":false,"in":"query","description":"Number of rows to skip (starts with 0)","schema":{"minimum":0,"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Comma-separated field names to sort by; prefix a field with \"-\" for descending, e.g. sort=name,-_md","schema":{"type":"string"}}],"responses":{"200":{"description":"Rows retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowsResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Table not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"tags":["Records"]}}}}
```

## Create records in a table

> Creates new records in an existing table (max. 6MB payload size)

```json
{"openapi":"3.0.0","info":{"title":"Ninox Public API","version":"1.0.0"},"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"API Key","description":"Enter your Workspace API key. \n\n API keys can be generated and managed within the **Workspace Integration** settings in the Ninox app.","in":"header","name":"Authorization","type":"http"}},"schemas":{"AddRowsBody":{"type":"object","properties":{"records":{"minItems":1,"type":"array","items":{"type":"object","additionalProperties":{}},"description":"Array of records to add to the target table"}},"required":["records"],"additionalProperties":false},"CreateRowsResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"},"description":"Created row ids"}}}},"required":["data"]},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"Error message"}}}},"required":["error"]}}},"paths":{"/api/v1/workspace/{workspaceId}/modules/{moduleName}/tables/{tableName}/records":{"post":{"operationId":"RowsV1Controller_addRows","summary":"Create records in a table","description":"Creates new records in an existing table (max. 6MB payload size)","parameters":[{"name":"moduleName","required":true,"in":"path","description":"Module name","schema":{"minLength":1,"type":"string"}},{"name":"tableName","required":true,"in":"path","description":"Table name","schema":{"minLength":1,"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"minLength":12,"maxLength":12,"pattern":"^[a-z0-9]+$","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddRowsBody"}}}},"responses":{"201":{"description":"Records created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRowsResponse"}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"tags":["Records"]}}}}
```

## Delete records from a table

> Deletes existing records from a table in a single transaction. If any record id is invalid, does not exist, or deletion fails, all changes are rolled back.

```json
{"openapi":"3.0.0","info":{"title":"Ninox Public API","version":"1.0.0"},"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"API Key","description":"Enter your Workspace API key. \n\n API keys can be generated and managed within the **Workspace Integration** settings in the Ninox app.","in":"header","name":"Authorization","type":"http"}},"schemas":{"DeleteRowsBody":{"type":"object","properties":{"records":{"minItems":1,"type":"array","items":{"type":"integer","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0},"description":"Array of record ids to delete"}},"required":["records"],"additionalProperties":false},"DeleteRowsResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"deletedIds":{"type":"array","items":{"type":"string"},"description":"Deleted row ids"}}}},"required":["data"]},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"Error message"}}}},"required":["error"]}}},"paths":{"/api/v1/workspace/{workspaceId}/modules/{moduleName}/tables/{tableName}/records":{"delete":{"operationId":"RowsV1Controller_deleteRows","summary":"Delete records from a table","description":"Deletes existing records from a table in a single transaction. If any record id is invalid, does not exist, or deletion fails, all changes are rolled back.","parameters":[{"name":"moduleName","required":true,"in":"path","description":"Module name","schema":{"minLength":1,"type":"string"}},{"name":"tableName","required":true,"in":"path","description":"Table name","schema":{"minLength":1,"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"minLength":12,"maxLength":12,"pattern":"^[a-z0-9]+$","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteRowsBody"}}}},"responses":{"200":{"description":"Records deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteRowsResponse"}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found (workspace, module, table, or record)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"tags":["Records"]}}}}
```

## Update records in a table

> Updates existing records in a table (max. 6MB payload size). Each record must include an \`id\` field referencing an existing record. The update is transactional — if any record fails validation or does not exist, all changes are rolled back.\
> \
> \*\*Field type rules:\*\*\
> \- \`multi\` — must be an \*\*array of strings\*\* (e.g. \`\["Gold", "Silver"]\`). A plain string is not accepted.\
> \- \`choice\` — a single string (e.g. \`"Gold"\`) or an array of strings.\
> \- \`boolean\` — \`true\` or \`false\` only; numeric \`1\`/\`0\` are not accepted.\
> \- \`number\` — a finite number or a numeric string (e.g. \`"42.5"\`).\
> \- \`date\` / \`timestamp\` / \`appointment\` — ISO 8601 string or Unix timestamp (ms).\
> \- Any field may be set to \`null\` to clear its value.

```json
{"openapi":"3.0.0","info":{"title":"Ninox Public API","version":"1.0.0"},"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"API Key","description":"Enter your Workspace API key. \n\n API keys can be generated and managed within the **Workspace Integration** settings in the Ninox app.","in":"header","name":"Authorization","type":"http"}},"schemas":{"UpdateRowsBody":{"type":"object","properties":{"records":{"minItems":1,"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0}},"additionalProperties":{}},"description":"Array of records to update (each must include an id field as a positive integer)"}},"required":["records"],"additionalProperties":false},"UpdateRowsResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"updatedIds":{"type":"array","items":{"type":"string"},"description":"Updated row ids"}}}},"required":["data"]},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"Error message"}}}},"required":["error"]}}},"paths":{"/api/v1/workspace/{workspaceId}/modules/{moduleName}/tables/{tableName}/records":{"patch":{"operationId":"RowsV1Controller_updateRows","summary":"Update records in a table","description":"Updates existing records in a table (max. 6MB payload size). Each record must include an `id` field referencing an existing record. The update is transactional — if any record fails validation or does not exist, all changes are rolled back.\n\n**Field type rules:**\n- `multi` — must be an **array of strings** (e.g. `[\"Gold\", \"Silver\"]`). A plain string is not accepted.\n- `choice` — a single string (e.g. `\"Gold\"`) or an array of strings.\n- `boolean` — `true` or `false` only; numeric `1`/`0` are not accepted.\n- `number` — a finite number or a numeric string (e.g. `\"42.5\"`).\n- `date` / `timestamp` / `appointment` — ISO 8601 string or Unix timestamp (ms).\n- Any field may be set to `null` to clear its value.","parameters":[{"name":"moduleName","required":true,"in":"path","description":"Module name","schema":{"minLength":1,"type":"string"}},{"name":"tableName","required":true,"in":"path","description":"Table name","schema":{"minLength":1,"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"minLength":12,"maxLength":12,"pattern":"^[a-z0-9]+$","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRowsBody"}}}},"responses":{"200":{"description":"Records updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRowsResponse"}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found (workspace, module, table, or record)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Payload too large (max 6 MB)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"tags":["Records"]}}}}
```

## Get a single record from a table

> Retrieves a single record by its id. The record id is not a filterable field, so this is the only way to fetch a known record by id.

```json
{"openapi":"3.0.0","info":{"title":"Ninox Public API","version":"1.0.0"},"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"API Key","description":"Enter your Workspace API key. \n\n API keys can be generated and managed within the **Workspace Integration** settings in the Ninox app.","in":"header","name":"Authorization","type":"http"}},"schemas":{"RowResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"values":{"type":"object","additionalProperties":{"anyOf":[{"type":"boolean"},{"type":"number"},{"type":"string"}],"nullable":true}}},"description":"The requested row"}},"required":["data"]},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"Error message"}}}},"required":["error"]}}},"paths":{"/api/v1/workspace/{workspaceId}/modules/{moduleName}/tables/{tableName}/record/{recordId}":{"get":{"operationId":"RowsV1Controller_getRow","summary":"Get a single record from a table","description":"Retrieves a single record by its id. The record id is not a filterable field, so this is the only way to fetch a known record by id.","parameters":[{"name":"moduleName","required":true,"in":"path","description":"Module name","schema":{"minLength":1,"type":"string"}},{"name":"recordId","required":true,"in":"path","description":"Record ID","schema":{"maximum":9007199254740991,"exclusiveMinimum":true,"type":"integer","minimum":0}},{"name":"tableName","required":true,"in":"path","description":"Table name","schema":{"minLength":1,"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"minLength":12,"maxLength":12,"pattern":"^[a-z0-9]+$","type":"string"}},{"name":"fields","required":false,"in":"query","description":"Comma-separated list of field names to include","schema":{"type":"string"}}],"responses":{"200":{"description":"Record retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Table or record not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"tags":["Records"]}}}}
```

## Get row changes since a timestamp

> Retrieves rows created, updated, or deleted since a given timestamp — for incremental sync. Only available for tables with history tracking enabled; returns 400 otherwise. Pass the response's \`meta.asOf\` as \`since\` on the next poll to avoid gaps or duplicates. When paging through one poll with \`limit\`/\`offset\`, also pass the first page's \`meta.asOf\` back as \`asOf\` on every subsequent page of that same poll.

```json
{"openapi":"3.0.0","info":{"title":"Ninox Public API","version":"1.0.0"},"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"API Key","description":"Enter your Workspace API key. \n\n API keys can be generated and managed within the **Workspace Integration** settings in the Ninox app.","in":"header","name":"Authorization","type":"http"}},"schemas":{"RowChangesResponse":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"changedAt":{"type":"string","description":"When the change happened (ISO 8601)"},"changeType":{"type":"string","enum":["create","delete","update"],"description":"Kind of change"},"id":{"type":"string","description":"Row id"}},"required":["changedAt","changeType","id"]},"description":"List of row changes"},"meta":{"type":"object","properties":{"asOf":{"type":"string","description":"Server timestamp to pass as `since` on the next poll"}}},"page_info":{"type":"object","properties":{"has_more":{"type":"boolean","description":"Whether more results are available"},"limit":{"type":"number","description":"Maximum results per page"},"offset":{"type":"number","description":"Current offset"}},"description":"Pagination information"}},"required":["data","meta","page_info"]},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"Error message"}}}},"required":["error"]}}},"paths":{"/api/v1/workspace/{workspaceId}/modules/{moduleName}/tables/{tableName}/records/changes":{"get":{"operationId":"RowsV1Controller_getRowChanges","summary":"Get row changes since a timestamp","description":"Retrieves rows created, updated, or deleted since a given timestamp — for incremental sync. Only available for tables with history tracking enabled; returns 400 otherwise. Pass the response's `meta.asOf` as `since` on the next poll to avoid gaps or duplicates. When paging through one poll with `limit`/`offset`, also pass the first page's `meta.asOf` back as `asOf` on every subsequent page of that same poll.","parameters":[{"name":"moduleName","required":true,"in":"path","description":"Module name","schema":{"minLength":1,"type":"string"}},{"name":"tableName","required":true,"in":"path","description":"Table name","schema":{"minLength":1,"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"minLength":12,"maxLength":12,"pattern":"^[a-z0-9]+$","type":"string"}},{"name":"asOf","required":false,"in":"query","description":"Pin the poll window to this timestamp (a prior response's `meta.asOf`) when fetching page 2+ of the same poll; omit on the first page","schema":{"format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of changes to return (1-100)","schema":{"minimum":1,"maximum":100,"type":"number"}},{"name":"offset","required":false,"in":"query","description":"Number of changes to skip (starts with 0)","schema":{"minimum":0,"type":"number"}},{"name":"since","required":true,"in":"query","description":"Return changes at or after this ISO 8601 timestamp","schema":{"format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","type":"string"}}],"responses":{"200":{"description":"Row changes retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowChangesResponse"}}}},"400":{"description":"Bad request (missing `since`, or table has history tracking disabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Table not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"tags":["Records"]}}}}
```

## Import records from CSV file

> Import records from a CSV file into an existing table.\
> &#x20;       \
> The endpoint accepts multipart/form-data with:\
> \- A CSV file (max 50 MB)\
> \- Optional parsing and import options\
> \
> \*\*Auto-mapping\*\*: By default, CSV columns are automatically mapped to table fields by matching column header names to field names (case-insensitive).\
> \
> \*\*Import Modes\*\*:\
> \- \`append\`: Always create new records (default)\
> \- \`update\`: Only update existing records (requires key field mappings)\
> \- \`upsert\`: Update existing records or create new ones (requires key field mappings)\
> \
> \*\*Field Mappings\*\*: Optionally provide explicit mappings to control which CSV columns map to which table fields, and which fields are used as keys for update/upsert operations.\
> \
> The entire import is processed within a single transaction - if any record fails to import, all changes are rolled back.

```json
{"openapi":"3.0.0","info":{"title":"Ninox Public API","version":"1.0.0"},"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"API Key","description":"Enter your Workspace API key. \n\n API keys can be generated and managed within the **Workspace Integration** settings in the Ninox app.","in":"header","name":"Authorization","type":"http"}},"schemas":{"ImportCSVResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"rowsImported":{"type":"number","description":"Number of rows successfully imported"},"rowsSkipped":{"type":"number","description":"Number of rows skipped (for update mode when no match is found)"},"rowsUpdated":{"type":"number","description":"Number of existing rows updated"}}}},"required":["data"]},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"Error message"}}}},"required":["error"]}}},"paths":{"/api/v1/workspace/{workspaceId}/modules/{moduleName}/tables/{tableName}/records/import/csv":{"post":{"operationId":"RowsV1Controller_importCSV","summary":"Import records from CSV file","description":"Import records from a CSV file into an existing table.\n        \nThe endpoint accepts multipart/form-data with:\n- A CSV file (max 50 MB)\n- Optional parsing and import options\n\n**Auto-mapping**: By default, CSV columns are automatically mapped to table fields by matching column header names to field names (case-insensitive).\n\n**Import Modes**:\n- `append`: Always create new records (default)\n- `update`: Only update existing records (requires key field mappings)\n- `upsert`: Update existing records or create new ones (requires key field mappings)\n\n**Field Mappings**: Optionally provide explicit mappings to control which CSV columns map to which table fields, and which fields are used as keys for update/upsert operations.\n\nThe entire import is processed within a single transaction - if any record fails to import, all changes are rolled back.","parameters":[{"name":"moduleName","required":true,"in":"path","description":"Module name","schema":{"minLength":1,"type":"string"}},{"name":"tableName","required":true,"in":"path","description":"Table name","schema":{"minLength":1,"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"minLength":12,"maxLength":12,"pattern":"^[a-z0-9]+$","type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"CSV file to import (max 50 MB)"},"hasHeader":{"type":"boolean","default":true,"description":"Whether the CSV file has a header row"},"delimiter":{"type":"string","description":"CSV delimiter character (auto-detected if not specified)"},"quoteChar":{"type":"string","default":"\"","description":"CSV quote character"},"encoding":{"type":"string","enum":["utf8","ascii","latin1","iso-latin1","utf16","windows-cp1252"],"default":"utf8","description":"CSV file encoding"},"importMode":{"type":"string","enum":["append","update","upsert"],"default":"append","description":"Import mode: append (insert all), update (update existing only), upsert (insert or update)"},"numberFormat":{"type":"string","enum":["us","european"],"default":"us","description":"Number format for parsing numeric values"},"batchSize":{"type":"integer","minimum":10,"maximum":200,"default":100,"description":"Number of records to process per batch"},"mappings":{"type":"string","description":"JSON array of field mappings: [{\"csvColumnName\": \"Name\", \"fieldName\": \"name\", \"updatePolicy\": \"key\"}]"}}}}}},"responses":{"201":{"description":"CSV import completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportCSVResponse"}}}},"400":{"description":"Bad request (invalid CSV, missing file, etc.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Table not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"File too large (max 50 MB)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"tags":["Records"]}}}}
```

## Upsert records in a table by a unique field

> Creates or updates records in a table, matched by a single explicit field (max. 6MB payload size). The match field must be marked \`unique: true\` on the table. Every record must include a value for the match field. An ambiguous match (more than one existing record with the same value) is rejected with a 409, never resolved silently.

```json
{"openapi":"3.0.0","info":{"title":"Ninox Public API","version":"1.0.0"},"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"API Key","description":"Enter your Workspace API key. \n\n API keys can be generated and managed within the **Workspace Integration** settings in the Ninox app.","in":"header","name":"Authorization","type":"http"}},"schemas":{"UpsertRowsBody":{"type":"object","properties":{"matchField":{"type":"string","minLength":1,"description":"Name of the unique field used to match existing records"},"records":{"minItems":1,"type":"array","items":{"type":"object","additionalProperties":{}},"description":"Array of records to upsert — each must include a value for matchField"}},"required":["matchField","records"],"additionalProperties":false},"UpsertRowsResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"createdIds":{"type":"array","items":{"type":"string"},"description":"Row ids created by this upsert"},"updatedIds":{"type":"array","items":{"type":"string"},"description":"Row ids matched and updated by this upsert"}}}},"required":["data"]},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"Error message"}}}},"required":["error"]}}},"paths":{"/api/v1/workspace/{workspaceId}/modules/{moduleName}/tables/{tableName}/records/upsert":{"post":{"operationId":"RowsV1Controller_upsertRows","summary":"Upsert records in a table by a unique field","description":"Creates or updates records in a table, matched by a single explicit field (max. 6MB payload size). The match field must be marked `unique: true` on the table. Every record must include a value for the match field. An ambiguous match (more than one existing record with the same value) is rejected with a 409, never resolved silently.","parameters":[{"name":"moduleName","required":true,"in":"path","description":"Module name","schema":{"minLength":1,"type":"string"}},{"name":"tableName","required":true,"in":"path","description":"Table name","schema":{"minLength":1,"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"minLength":12,"maxLength":12,"pattern":"^[a-z0-9]+$","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertRowsBody"}}}},"responses":{"200":{"description":"Records upserted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertRowsResponse"}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found (workspace, module, table, or match field)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Ambiguous match — more than one record matches the given value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Payload too large (max 6 MB)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"tags":["Records"]}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ninox.com/ninox-api/api-reference/api-endpoints/records.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
