Comparing REST API v3 and v4
10 minutes to readREST API v4 covers everything available in v3 and adds capabilities for record-level access, bulk operations, schema discovery, and AI integrations. This article walks through the differences in URL structure, query parameters, request bodies, response formats, and status codes, and ends with an endpoint-by-endpoint mapping you can use as a migration reference.
Compare v3 and v4 at a glance
| v3 | v4 | |
|---|---|---|
| Operations | 70 endpoints | 107 endpoints |
| Authentication | OAuth 2.0 bearer token | OAuth 2.0 bearer token (unchanged) |
| Object addressing | Mixed: tables and views by name, others by externalKey or ID |
Consistently by ID: a 6-character alphanumeric code or GUID |
| Query parameters | q.-prefixed, such as q.where |
Plain camelCase, such as where |
| List response body | Result array; pagination on request |
data plus pagination, always included |
| JSON property naming | PascalCase, such as Name, Type |
camelCase, such as name, dataType |
| Partial updates | PUT with a q.where query parameter |
PATCH, with conditions in the request body |
| Single-record access | Not available; list plus WHERE only | GET, PATCH, PUT, DELETE by PK_ID |
| Bulk operations | Limited to multi-file upload | Dedicated /bulk endpoints for records, users, attachments |
| Echo written records | response query parameter |
echo=true on all write endpoints |
| Error responses | HTTP status codes only | Structured body with code, request ID, hint, and docs link |
| Response formats | JSON for all, XML for selected operations | JSON only |
| Text data type names | STRING, TEXT |
TEXT255, TEXT64K |
Address objects by ID
v3 addressing was mixed. Tables and views used names, as in /v3/tables/{tableName}/records, while files, folders, Bridge applications, and data import/export tasks used an externalKey GUID, and webhooks, events, and directories already used IDs.
v4 addresses every object by ID. Tables, views, and tasks use a 6-character alphanumeric identifier, as in /v4/tables/{tableId}/records. Files, folders, and applications use a GUID.
For the objects that moved from names to IDs, the identifier stays stable when the object is renamed, so integrations no longer break because someone renamed a table or view. Look up IDs with GET /v4/schemas/tables or the list endpoints.
Renamed resources
| Area | v3 | v4 |
|---|---|---|
| Files area | /v3/files |
/v4/fileAssets/files |
| Folder parameter | externalKey (GUID) |
folderId (GUID) |
| File identifier | externalKey (GUID) |
fileId (GUID) |
| App identifier | externalKey (GUID) |
appId (GUID) |
| DataPages casing | datapages |
dataPages |
Update query parameters and property names
v3 query parameters carried a q. prefix: q.select, q.where, q.orderBy, q.groupBy, q.limit, q.pageNumber, q.pageSize, q.sortField, q.sortDescending. v4 keeps the same set as plain camelCase names without the prefix.
Ranges and defaults are unchanged. limit accepts 1 to 1,000 with a default of 100, and is ignored when paging. pageSize accepts 1 to 1,000 with a default of 25 when pageNumber is set.
JSON properties are camelCase
| Context | v3 | v4 |
|---|---|---|
| List envelope | Result |
data |
| Pagination | TotalCount, PageNumber, PageSize |
totalCount, pageNumber, pageSize |
| Field definition | Name, Type, Label, Unique |
name, dataType, label, unique |
| Timestamp options | OnInsert, OnUpdate, TimeZone |
stampOnInsert, stampOnUpdate, stampTimeZone |
| Table creation | { "Name": …, "Fields": [...] } |
{ "name": …, "description": …, "fields": [...] } |
| Notes property | Notes |
description |
Record data itself is unaffected. Your field names are returned exactly as defined in the table, and the system primary key remains PK_ID in both versions. In v4 it can also be used directly as a record identifier in requests. Query and read data
Response envelope
v3 list responses wrapped records in a Result array and returned pagination information only when you set q.getPaginationInfo=true. v4 list responses always return data plus a pagination object, and the getPaginationInfo parameter is gone.
v3
GET /rest/v3/tables/Customers/records?q.where=Status='Active'&q.limit=50
{
"Result": [ { "PK_ID": 1, "Name": "Acme", "Status": "Active" }, … ]
}v4
GET /rest/v4/tables/a1b2c3/records?where=Status=N'Active'&limit=50
{
"data": [ { "PK_ID": 1, "Name": "Acme", "Status": "Active" }, … ],
"pagination": { "totalCount": 137, "pageNumber": 1, "pageSize": 50 }
}T-SQL expressions in query parameters
In v4, the select, where, orderBy, and groupBy parameters accept full T-SQL expressions. That includes aggregates such as COUNT(*) and SUM(Amount), CASE expressions, arithmetic, and correlated subqueries against any table or view your API profile can access. groupBy supports HAVING for aggregate filtering.
Every referenced object is validated against your profile’s permission boundary before execution. DDL statements and system catalogs are always blocked.
GET /rest/v4/tables/a1b2c3/records
?select=Region, COUNT(*) AS Orders, SUM(Amount) AS Total
&groupBy=Region HAVING COUNT(*) > 5
&orderBy=Total DESCTwo syntax conventions apply to v4 WHERE clauses. Prefix string literals with N for Unicode, as in Status=N'Active', and escape quotes by doubling them, as in N'O''Brien'. Use 1 and 0 for Yes/No fields in conditions, while sending JSON true and false in write payloads.
Richer list metadata
v3’s GET /v3/tables returned a plain array of table names. v4’s GET /v4/tables returns full metadata for each table: tableId, name, description, fieldCount, lastModified, modifiedBy, dateCreated, createdBy, plus which Bridge apps, Flex apps, and triggered actions use the table.
Field definitions also expose more. Formula fields return their formula text, and lookup fields return a relationship object describing referencedTable, referencedField, relationshipType, and referentialIntegrity. A description can also be set in POST /v4/tables.
Schema discovery in one call
The /v4/schemas endpoints return every accessible object with its complete field definitions, and for tables its relationship definitions, in a single request. In v3, discovering the shape of an account required one request per table or view. This is the recommended first call for any new v4 integration.
GET /rest/v4/schemas/tables GET /rest/v4/schemas/views GET /rest/v4/schemas/directories GET /rest/v4/schemas/outgoingWebhooks
Write data
Single-record operations
v3 had no way to address one record. Reads, updates, and deletes always operated on the collection filtered by q.where. v4 adds record-level endpoints keyed by PK_ID.
GET /v4/tables/{tableId}/records/{recordPkId}
PATCH /v4/tables/{tableId}/records/{recordPkId} (partial update)
PUT /v4/tables/{tableId}/records/{recordPkId} (update)
DELETE /v4/tables/{tableId}/records/{recordPkId}PATCH updates only the fields you include. Unspecified fields are left unchanged, and passing null clears a field. The same record-level pattern applies to views at /v4/views/{viewId}/records/{recordPkId} and to directory users at /v4/directories/{directoryId}/users/{userId}, keyed by UserGUID.
Conditional updates move to /bulk
The v3 collection update becomes an explicit bulk operation in v4, and the WHERE condition moves from a query parameter into the JSON body.
v3
PUT /rest/v3/tables/Customers/records?q.where=Status='Prospect'
Body: { "Status": "Active" }v4
PATCH /rest/v4/tables/a1b2c3/records/bulk
Body: {
"where": "Status=N'Prospect'",
"recordValues": { "Status": "Active" }
}Bulk DELETE works the same way, using DELETE /v4/tables/{tableId}/records/bulk with { "where": "..." } in the body. Keeping the condition in the body avoids URL-encoding problems with complex WHERE clauses, a common source of v3 integration bugs.
1=1 are rejected on bulk writes when the tautology guard is active. Scope bulk writes with a targeted condition, and verify the match count with a GET first.Bulk inserts with per-record status
POST /v4/tables/{tableId}/records/bulk accepts an array of up to 1,000 records. If every record succeeds you get 201 Created with the new PK_ID values. If some fail you get 207 Multi-Status with a per-record result array in request order, showing each record’s individual status code, so you can retry only the failures. v3 had no bulk insert, and each record required its own POST.
Two related endpoints are also new. POST /v4/tables/{tableId}/records/bulk/attachments uploads one file to multiple records and multiple attachment fields in a single request. PATCH /v4/tables/{tableId}/records/bulk/attachments/{fieldName}/fileInfo renames table attachment files that match a condition.
Echo replaces the response parameter
v3 write endpoints used a response query parameter to control the response type. v4 standardizes this: every write endpoint accepts echo=true to return the affected records. Without it, a POST returns just the PK_ID, and updates return the affected-record count.
Review changes by resource
Tables
GET /v4/tables/{tableId}/records/bulk/attachments/{fieldName}/fileInfo returns file metadata for multiple records in one call. v3 could return metadata for only one file per request, so multiple requests were needed.
Files become File Assets
- All paths move from
/v3/filesto/v4/fileAssets/filesand/v4/fileAssets/folders, andexternalKeybecomesfolderIdorfileId. - New: search files or folders by name across the whole account with
GET /v4/fileAssets/files/search?name=…. - New: create folders with
POST /v4/fileAssets/folders. v3 could only list them. - New:
fullFilePathandfullFolderPathproperties in GET, PUT, and POST operations simplify uploading files to tables. Upload the file to All assets first, then use thefullFilePathreturned in the response to update a File data type field. - Clearer status codes: multi-upload
POST …/files/bulkreturns409 Conflictfor name collisions, PUT returns201for a new file and200for an overwrite, and DELETE returns204 No Content.
Directories
- v3 returned directories and their users alongside other tables and records under
/v3/tables. v4 returns them only under/v4/directories, which is cleaner now that permissions are separated by resource type. - v3 could only update or delete users in bulk using a
Wherequery parameter. v4 adds per-user endpoints keyed byUserGUIDatPATCHandDELETE /v4/directories/{directoryId}/users/{userId}, alongside/users/bulkendpoints with the condition in the body. - User activation moves the
UserGUIDinto the path:POST …/users/{userId}/activate. - New: directory field design. Create, update, and delete directory fields programmatically with
POST,PATCH, andDELETE …/fields. v3 had no directory design endpoints. - New: attachment files on user records. Download, upload, and delete per user, plus bulk metadata and rename.
- Directory queries return directory-designated fields plus system attributes
_status,_sign_in_method, and_2fa_status.
Outgoing webhooks
- Webhook and event updates change from PUT to PATCH. Create, read, and delete are unchanged.
- Webhook event creation uses
objectId, either atableIdordirectoryId, instead ofobjectName. GET /v4/outgoingWebhooks/{webhookId}/eventsaccepts aneventTypequery parameter:table.recordInsert,table.recordUpdate, ortable.recordDelete.- New:
PATCH …/regenerateSecretrotates a webhook’s signing secret without recreating the webhook. - New:
GET /v4/schemas/outgoingWebhookslists all webhooks with their event definitions in one call.
Bridge apps, Flex apps, and tasks
- Bridge application endpoints are functionally unchanged.
{externalKey}becomes{appId}, and bulk deployment moves to…/dataPages/bulk/deployment. - New: read access to Flex applications with
GET /v4/flexApplicationsandGET /v4/flexApplications/{appId}. - Data import/export task endpoints return both active and inactive tasks, and are identified by a new 6-character alphanumeric
{taskId}instead of the{externalKey}GUID used in v3.
Handle errors
{
"Code": "IncorrectQueryParameter",
"Message": "The WHERE clause references a field that does not exist.",
"Resource": "/v4/tables/a1b2c3/records",
"RequestId": "7f3a9c…",
"DocumentationUrl": "…/v4/errors/IncorrectQueryParameter",
"Hint": { "Note": "…", "Remediation": "…" }
}
Code is machine-readable for programmatic handling, RequestId speeds up support investigations, and Hint carries remediation guidance.
Also plan for 207 Multi-Status on bulk inserts. Structural deletes, such as deleting a table, folder, or Flex app, intentionally return 405, because schema deletion is not exposed through the API by design. Build for AI and automation
v4 is designed to be consumed by AI agents and MCP integrations, not only by hand-written code. The specification embeds a machine-readable domain primer in the OpenAPI info.x-caspio-ai-context extension, and GET /v4/aiManifest serves that context along with focused specification lenses: data manager for runtime record work, designer for structural work, and automation for webhooks and triggered actions. Tools can load only the operations they need instead of the full 107-operation specification.