standards
Requests and recordsHTTP API

Requests and records

Read, filter, write and search records with the envelope, the sixteen operators and the lifecycle every object shares.


An object is a table you declare: a name, a label expression, and a list of attributes. A record is one row of that object. Every object gets the same REST surface under /records/:object, the same envelope, the same filters and the same lifecycle, so what you learn on one object applies to all of them.

Objects

An attribute has one of sixteen types. Each is declared with a builder from @standards/client; see Data model / Attributes for options and inferred types.

TypeHolds
textA short string.
richtextFormatted text. Not filterable, except for emptiness.
numberA number, optionally with a unit and precision.
checkboxtrue or false.
dateA calendar date or a timestamp.
phoneA phone number, written as { countryCode, phoneNumber }, filtered as digits.
currencyAn amount and a currency code.
statusOne value from a declared list, with a colour.
selectOne value from a declared list.
multiselectSeveral values from a declared list.
locationA postal address; filter on city, postalCode or country.
userA workspace user.
relationA link to records of another object, single or .many().
formulaA value computed from the record.
rollupA value aggregated across a relation.
documentA pack of files attached to the record.

Relations link records across objects, and a bilateral relation keeps both sides in step. Documents hold files under a record. Views describe how the app lists and opens records. Data model / Relations and documents and Interfaces / Views cover them.

Records

Every record carries these fields beside its attribute values:

FieldMeaning
idUUID of the record.
objectNameName of the owning object.
labelDisplay name computed from the object's label expression.
valuesAttribute values, keyed by attribute name. The SDK flattens values onto the record.
visibilityworkspace or private.
ownedByThe owning actor of a private record, null otherwise.
viewerAccessWhat you may do with the record: owner, editor, reader or workspace.
appearanceThe record's own icon or image, never both. image is { fileId, source }: a public file id, and where it came from. Absent when it has neither: the record then shows its object's. See Appearance.
metadataFree-form object you can write, for UI state or your own flags.
createdAt, updatedAtTimestamps.
createdBy, lastUpdatedByActor ids.
deletedAt, deletedBySet while the record sits in the trash.
versionMonotonic version of the record. It moves on every write, including computed attributes recalculated in the background, so the higher of two copies of a record is the newer.
schemaVersionLegacy schema version stamp; nothing migrates records by it any more.

These names are reserved. An attribute cannot be called id, label, values, metadata or appearance.

Reading

Three routes read a collection. All three answer the same envelope. A fourth, POST /records/:object/aggregate, measures the same records, optionally grouped (see Aggregating).

RouteUse it for
GET /records/:objectQuick reads with limit, offset, countMode and deleted=true in the query string.
POST /records/:object/listFilters, sorts, a visibility scope, fields and a timezone in the body.
POST /records/:object/searchFull-text search. q is required; the body takes every list option on top.
curl https://api.standards.new/v1/records/contacts/list \
  -H "Authorization: Bearer $STANDARDS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "limit": 20, "sorts": [{ "attribute": "lastName", "direction": "asc" }] }'
{
  "data": [{ "id": "…", "objectName": "contacts", "label": "Ada Lovelace", "values": { "lastName": "Lovelace" } }],
  "page": { "hasMore": true, "total": null, "countMode": "none", "nextOffset": 20 }
}

GET /records/:object/:id reads one record. Add ?includeDeleted=true to read one from the trash.

Pagination and counting

  • limit defaults to 20. Anything above 1000 is clamped to 1000. On list and search it must be an integer of 0 or more: -1, 1.5, "20" or null is a 400 SCHEMA_VALIDATION_FAILED naming limit.
  • limit: 0 returns no rows. With a countMode, it is the cheapest way to count.
  • offset defaults to 0. It must stay below 2147483647 - limit, or the request is a 400.
  • countMode is none (default), estimated or exact. With none, total is null and the database skips the count. Send exact only when a number is displayed.
  • hasMore tells you whether a next page exists. Follow page.nextOffset when present rather than computing it from data.length: search can return an empty page that still advances the cursor.

Aggregating

POST /records/:object/aggregate measures the records a typed query selects, in one request: a count, or the sum, average, minimum or maximum of one attribute, optionally grouped. Reads are the same as list: object read permission and the viewer's visibility. Only records you can read are measured. The body is strict: a search term (q) or any unknown key is a 400.

curl https://api.standards.new/v1/records/deals/aggregate \
  -H "Authorization: Bearer $STANDARDS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "query": { "source": { "object": "deals" } }, "measure": "sum", "attribute": "amount", "groupBy": "stage" }'
{
  "groups": [{ "key": "open", "value": 4200 }, { "key": "won", "value": 9000 }, { "key": null, "value": 300 }],
  "total": 13500,
  "truncated": false,
  "currency": "EUR"
}
FieldNotes
queryRequired. The records measured, as query(...).toDefinition() builds it. Its sorts and limit are ignored.
measureRequired. count, sum, avg, min or max.
attributeThe number or currency attribute measured. Required unless measure is count.
groupByA status, select, single user or single relation attribute by name, or { "attribute": "createdAt", "bucket": "month" } to cut a date attribute into day, week, month, quarter or year buckets; a date attribute named alone is cut into months.
groupBy2A second grouping, for stacked or funnel charts. Answers key2 on each group.
compare{ "attribute", "period" }: restricts the measure to the current period of a date attribute so far, up to now, and answers in previous the same stretch of the period before: on the 10th, the 1st to the 10th of last month, ending at that period's own end when it is shorter. 7d and 30d are the last 7 or 30 days against the ones before. period is a bucket, 7d or 30d.
userFiltersThe reader's own filters, ANDed with the query's.
timezoneIANA zone of the buckets and comparison windows. Defaults to UTC.
hostRecordIdThe record a relation query reads from, on a record page.
  • key is the stored option value, the id of the user or related record, or the bucket start. null groups the records with no value, and a value outside the attribute's options is its own group.
  • avg, min and max answer null for a group with no values. Summing amounts in several currencies is refused with AGGREGATE_MIXED_CURRENCY.
  • currency is the code the amounts were measured in, which is the records' own currency and not the attribute's default: records all in USD on an attribute that defaults to EUR answer "USD". It is present only for sum, avg, min or max over a currency attribute that met at least one record, in the current or the previous period.
  • A multi-valued attribute cannot be grouped on, and a related record you cannot read counts in the null group.
  • At most 100 groups. A field grouping comes largest first; when more groups exist, truncated is true. total still measures every matching record, the cut groups included.
  • A date grouping comes oldest bucket first and is never cut: when it would answer more than 100 groups (counted with the second grouping and the null group), the next coarser bucket that fits answers (day, then week, month, quarter, year), and appliedBucket names the one used (appliedBucket2 for groupBy2). A bucket with no record is absent, not 0.

From the SDK, the same call is the aggregate() terminal of a records query (Records).

Filtering

A filter is one combinator, and or or, over a flat list of rules. There is no nesting.

{
  "combinator": "and",
  "rules": [
    { "attribute": "status", "operator": "any_of", "value": ["lead", "active"] },
    { "attribute": "createdAt", "operator": "greater_than", "value": "2026-01-01" }
  ]
}

Sixteen operators exist. Which ones apply depends on the attribute type.

OperatorTypesMeaning
istext, phone, number, currency, checkbox, status, select, date, user, relation, formulaEquals. On a checkbox, value is a boolean.
is_nottext, phone, number, currency, status, select, date, user, relation, formulaDoes not equal.
containstext, phone, formulaCase-insensitive substring.
not_containstext, phone, formulaSubstring absent.
starts_withtext, phoneCase-insensitive prefix.
ends_withtextCase-insensitive suffix.
greater_thannumber, currency, date, formulaStrictly after or above. For dates, see Dates below.
greater_or_equalnumber, currency, date, formulaOn or after, at or above.
less_thannumber, currency, date, formulaStrictly before or below.
less_or_equalnumber, currency, date, formulaOn or before, at or below.
any_ofstatus, select, multiselect, user, relationAt least one stored value is in the list.
none_ofstatus, select, multiselect, user, relationNo stored value is in the list.
is_emptyevery type but checkbox and rollupNo value. value is null.
is_not_emptyevery type but checkbox and rollupHas a value. value is null.
is_withindateRelative range: { "amount": 7, "unit": "days", "direction": "past" }.
on_day_monthdateAnniversary, any year: { "day": 14, "month": 7 }.

System fields filter too: id, label, createdAt, updatedAt, createdBy, lastUpdatedBy and the others take the operators of their kind.

Dates. A date attribute without a time compares by UTC calendar day: greater_than "2026-09-18" starts on the 19th, and a value with a time counts as the day it is written on (2026-09-18T23:30:00-05:00 is the 18th). An attribute declared with .includeTime() compares instants, and the value sets the granularity. A bare day YYYY-MM-DD stands for that whole UTC day: is "2026-09-18" matches any time that day and greater_than starts at 2026-09-19T00:00:00Z. A value with a time is one exact instant, so is matches that instant only. A value without an offset is read as UTC. createdAt, updatedAt and deletedAt do not follow this rule yet; filter them with a bare day, which every operator reads as a UTC calendar day. For an absolute comparison (is, is_not, less_than, less_or_equal, greater_than, or greater_or_equal), the comparand must name a real calendar day: 2026-09-18, optionally followed by a time — 2026-09-18T14:30, 2026-09-18T14:30:00Z, 2026-09-18T14:30:00+02:00, 2026-09-18 14:30:00. Seconds and their fraction are optional, and the offset may be written Z, ±HH or ±HH:MM up to 14 hours. This also applies to date-valued relation or document properties and date-returning formulas. Anything else — text that is not an ISO date ("Sept 18 2026", "22-09-18"), an impossible day ("2026-02-30"), or a number — is a validation error: /records/<object>/list and /search both answer 400, and the filter never runs. Relative is_within and on_day_month rules keep their object values.

Aliases. On input, the API also accepts eq, neq, lt, gt, lte, gte, in, notIn, not_in, and the camelCase forms (startsWith, isEmpty, doesNotContain …). They are normalized to the canonical name above before validation and never stored. The former before, after, on_or_before, on_or_after, is_checked, is_not_checked and day_month_eq are rejected with a message naming the replacement.

Dynamic values. Three values resolve at query time:

{ "attribute": "owner", "operator": "is", "value": { "dynamic": "actor", "ref": "current" } }
{ "attribute": "dueDate", "operator": "is", "value": { "dynamic": "date", "anchor": "today" } }
{ "attribute": "updatedAt", "operator": "less_than", "value": { "dynamic": "date", "anchor": "now" } }

The actor token (@me) matches the caller, on a user attribute as on createdBy. In an agent turn it also matches the person the agent is working for, so "my deals" asked of an agent means the asker's deals. A scheduled run's @me also names the routine's creator when a person created it; a trigger-fired run has no asker, and its @me is the agent alone. @me names the same people the read is scoped to, so it only narrows: records the caller cannot see stay hidden. today resolves to a calendar day in the request's timezone, which defaults to UTC; send timezone in the body or an x-timezone header (Europe/Paris) so today means the caller's today. now is the current UTC instant, so is with now on an attribute with a time matches almost nothing; bound it with greater_or_equal and less_than, or use is_within. today is a bare day, whose bounds are UTC. When @me cannot resolve, the rule is dropped, not rejected.

Relation and document properties. When a relation carries qualified properties, add property to filter on the property and quantifier (any or none) to say how many linked records must match:

{ "attribute": "members", "property": "role", "operator": "is", "value": "admin", "quantifier": "any" }

A location filters the same way, on property: "city", "postalCode" or "country" (an ISO3 code), with the text operators and no quantifier.

Sorting

sorts is an ordered list of { attribute, direction }, asc or desc. Sorts apply in the order given.

Reference hydration

By default every relation, document and user attribute comes back as the linked records. fields narrows that:

  • omit fields: every reference attribute is hydrated;
  • "fields": ["company"]: only those attributes are hydrated;
  • "fields": []: nothing is hydrated and the keys are absent.

Skipping hydration is the cheapest read for exports.

Writing

Create

POST /records/:object takes the values under data and options beside them:

curl https://api.standards.new/v1/records/contacts \
  -H "Authorization: Bearer $STANDARDS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "data": { "firstName": "Ada", "lastName": "Lovelace" }, "options": { "visibility": "private" } }'
  • options.allowDraft skips required-attribute validation. The record is created and nothing marks it incomplete.
  • options.visibility sets workspace (default) or private. A visibility beside data is refused with 400, so a record meant to be private is never created workspace-visible.
  • options.linkTo ({ objectName, recordId, attribute, base }) creates the record and links it to an existing one in one transaction. base is the value of the owner's attribute as you last read it (an id, a list of ids for a many relation, null when empty): if the owner's value changed since, the create answers 409 SCHEMA_CONFLICT and nothing is created. Without base, the link is written over whatever the owner holds. Any other key in linkTo is refused with 400.

The answer is 201 with the record.

Update

PUT /records/:object/:id is a partial update: send only the attributes that change. The body is the values object itself, with no data wrapper. An empty body is a 400.

curl -X PUT https://api.standards.new/v1/records/contacts/$RECORD_ID \
  -H "Authorization: Bearer $STANDARDS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]" }'

PUT writes unconditionally: the last write wins. An If-Match header is refused with 400 SCHEMA_VALIDATION_FAILED. To write against what you read, send the change as an operation, where a step's base holds, per attribute, the value you based the change on. For a scalar it is a precondition: if the stored value moved since, that attribute is not written and the step answers conflict with the current value (null, "" and an absent value count as the same empty). For a relation or a document it is the base of a three-way merge, so members others added or removed meanwhile are kept. Without base, the last write wins. Every operation returns the inverse computed by the server, so a client can undo it. Sending null, "" or [] clears an attribute, required or not: required() gates creation, not later writes.

Delete, restore, purge

ActionRoutePermissionEffect
DeleteDELETE /records/:object/:iddeleteMoves the record to the trash. 204.
RestorePOST /records/:object/:id/restoredeleteBrings it back.
PurgePOST /records/:object/:id/purgemanageRemoves it for good. 409 unless it is already in the trash.

GET /records/:object?deleted=true lists the trash.

Bulk

RouteBodyMax itemsResult
POST /records/:object/bulk-create{ "records": [ … ], "options"? }100{ created, errors: [{ index, error }] }
POST /records/:object/bulk-update{ "updates": [{ "id", "data" }] }100{ updated, errors: [{ id, error, code }] }
POST /records/:object/bulk-delete{ "ids": [ … ] }100{ deleted, errors: [{ id, error, code }] }

A batch above its cap is refused whole with 400 SCHEMA_VALIDATION_FAILED naming the array; nothing is written. Where the API's rate limiter is on, the bulk routes also allow 10 calls a minute per client IP, so the effective ceiling is 1,000 items a minute: an 11th call inside the minute answers 429 with a Retry-After header in seconds — wait that long, then retry. Each item is validated before the batch runs. A failed item lands in errors while its siblings proceed; the batch is not transactional, so a bulk update that reports one error has still written the others.

Bulk create makes workspace records only. A visibility, in options or beside records, is refused with 400 rather than ignored; create private records one by one with options.visibility.

A bulk update or bulk delete error always carries a code: SCHEMA_FORBIDDEN for a record you can read but not change (one shared with you), SCHEMA_RECORD_NOT_FOUND for a missing or hidden id, SCHEMA_SYSTEM_ENTITY_IMMUTABLE for a record the platform owns, such as a memory page, SCHEMA_REPOSITORY_DELETE_FAILED for a delete the database rejected.

Operations

POST /operations writes several records in one request and answers each step with the record as it now stands, plus the operation that undoes it.

curl https://api.standards.new/v1/operations \
  -H "Authorization: Bearer $STANDARDS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "opId": "5b1f0c9e-3a4d-4e8b-9c2f-7d6e5a4b3c21",
    "steps": [
      { "kind": "record.set", "objectId": "'$CONTACTS_ID'", "recordId": "'$RECORD_ID'",
        "values": { "email": "[email protected]" }, "base": { "email": "[email protected]" } }
    ]
  }'

The body holds an opId (a UUID you generate) and steps: 1 to 100, at most one per record. objectId is the object's UUID, the id that GET /schema/objects/:name returns. Each step is one of five kinds:

kindFieldsEffect
record.createobjectId, recordId, values, options?Creates the record under the recordId you chose. options takes visibility, allowDraft and linkTo, as on create.
record.setobjectId, recordId, values, base?A partial update. base holds, per attribute, the value you based the change on, checked as under update.
record.deleteobjectId, recordIdMoves the record to the trash.
record.restoreobjectId, recordIdBrings it back from the trash.
record.patchobjectId, recordId, attribute, editA partial edit of one rich text attribute (any other attribute answers SCHEMA_VALIDATION_FAILED). edit is { "command": "replace", "oldText", "newText", "replaceAll"? } or { "command": "insert", "text", "after"? }: oldText (and after) must occur exactly once in the current value unless replaceAll is set; without after, text is appended. oldText, text and after are never empty. The edit applies to the value as stored when the write lands, even if it changed since you read it, and the result is checked as a record.set of that value would be. An edit that changes nothing answers applied and writes nothing.

Each step is checked against your permissions on its object, as the matching REST route checks it, and answers on its own: one refused step does not undo the others. The answer is 201 with { opId, steps, inverse }, steps in the order you sent them:

statusFieldsMeaning
appliedrecordThe step was written.
conflictrecord, attrs: [{ attr, current, by }]A record.set whose base no longer held, or a record.patch whose anchor is no longer in the value or now occurs more than once, for the attributes listed: those were not written, the others were. current is the stored value, by the record's last writer (not necessarily who changed that attribute).
errorrecordId, code, message?The step was refused with an error code. Only SCHEMA_VALIDATION_FAILED carries a message; any other reason travels as the code alone. A step on an unknown object, or on one closed to API writes, answers SCHEMA_OBJECT_NOT_FOUND.

inverse is an operation that undoes what applied, ready to send back to POST /operations: a create is undone by a delete, a delete by a restore, a restore by a delete, a set by a set of the previous values whose base is what this operation wrote, so undoing never overwrites a later change, and a patch by a set of the previous value on the same terms. It is null when nothing was written.

A malformed body, more than 100 steps or two steps on one record is refused whole with 400 SCHEMA_VALIDATION_FAILED, and nothing is written.

Attribution of agent writes

A defined agent writes as its own actor: the audit trail records actorType: "agent" with the human principal as rootActorId, and the activity feed shows the agent's face beside the person it acted for, read as “Lily for Karl”. A routine or an API key acting for someone reads the same way. A general chat without an agent definition writes as the person who asked, so that person stays the author in the activity feed, shown as “Karl via General chat”.

Every audit entry written during either kind of agent turn carries metadata.agentSessionId, identifying the conversation it was written in. On those rows, for both single and batch writes, the name of the agent, the routine or the general chat links to the conversation or the routine it was written in, when you can open it. A manual edit outside an agent turn carries no such key, names only its author and links nowhere. This metadata belongs to the audit entry, not the record's writable metadata field.

Visibility

A record is workspace or private. A private record is returned only to its owner and to the members it is shared with (Record access); anyone else gets the same 404 as for a missing id. List and search accept a visibility scope to read one side only; a list also accepts shared, the private records other members shared with you. Hydration and totals never leak private records.

A relation write keeps the members you cannot see: the stored membership is the value you sent plus the private records hidden from you, and the response never names them. A single relation whose target is hidden from you keeps it when you clear the field, and gives way to the value when you choose one.

PATCH /records/:object/:id/visibility with { "visibility": "private" } flips a record. Only the creator can make a workspace record private, and only the owner can open it back up. ownedBy is set by the server and cannot be assigned. A memory page takes the visibility of what it is about, so the route refuses it with 403 SCHEMA_SYSTEM_ENTITY_IMMUTABLE; change the visibility of the record or agent the page belongs to instead.

Appearance

A record can carry its own icon or its own image, never both: setting one removes the other. Each route needs update permission on the object and write access to the record. A reader the record is shared with gets 403 SCHEMA_FORBIDDEN; anyone who cannot see it gets the same 404 as for a missing id. Each answers the record as written, with its new appearance; reference attributes are not hydrated in that answer.

RouteBodyEffect
PUT /records/:object/:id/appearance{ "icon": "rocket" }Sets the icon. Any other key, a file id included, is a 400.
PUT /records/:object/:id/imagemultipart, one fileUploads the image as a public file of your workspace and sets it.
PUT /records/:object/:id/image/logo{ "domain": "acme.com" }Sets a company's logo as the image.
DELETE /records/:object/:id/appearancenoneRemoves the icon or the image. On an object declaring .image({ from }), its image is then looked up again from its data.
curl -X PUT https://api.standards.new/v1/records/companies/$RECORD_ID/image \
  -H "Authorization: Bearer $STANDARDS_API_KEY" \
  -F "[email protected]"

The image is a PNG, JPEG, GIF, WebP or AVIF of at most 5 MiB, checked against its bytes: an SVG, or a file whose bytes are not the type it is declared as, is a 400 SCHEMA_VALIDATION_FAILED, and a larger file a 413. The record keeps its previous appearance. The image you replace or remove is deleted. A deployment without public file links answers 503 CONFIGURATION_REQUIRED.

The image is a public file: {publicFilesUrl}/{fileId} opens it without signing in. {publicFilesUrl}/{fileId}?variant=mark serves it resized to a 128 px square, the size to use for an avatar or a list mark.

appearance.image.source says where the image came from. Every image you set through these routes, a file or a logo picked with the route below, is upload. website, name and email mark an image Standards found on its own from that attribute of an object declaring .image({ from }) (see Record images). A found image never replaces an icon or an image someone set, and it follows the record's data.

Logo as image

PUT /records/:object/:id/image/logo sets a company's logo as the record's image. domain is a website or a bare domain: https://www.Acme.com/about reads as acme.com. The logo counts as chosen (source: "upload"), and like an upload it removes the icon and replaces the previous image.

curl -X PUT https://api.standards.new/v1/records/companies/$RECORD_ID/image/logo \
  -H "Authorization: Bearer $STANDARDS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "acme.com" }'
  • A value that is not a domain, a domain with no known logo, or a deployment without a logo service (logosConfigured: false) answers 400 SCHEMA_VALIDATION_FAILED, and the record keeps its appearance. So does a logo the service is still preparing, or a workspace that has looked up too many new domains today: try again later.
  • When the logo service is unavailable the route answers 503 LOGO_PROVIDER_UNAVAILABLE.
  • A logo is stored once per workspace: two records set to the same domain share one file, and removing it from one record leaves it on the other.

GET /logos/:object/search?query= lists at most five company logos matching query, to set as the image of a record of :object. It needs update permission on the object; without it the route answers 403 SCHEMA_FORBIDDEN.

curl "https://api.standards.new/v1/logos/companies/search?query=acme" \
  -H "Authorization: Bearer $STANDARDS_API_KEY"
[{ "name": "Acme", "domain": "acme.com", "previewUrl": "https://img.logo.dev/acme.com?…" }]
  • query is lowercased, trimmed and its spaces collapsed before it is read. Under two characters the answer is []; over 120 it is a 400 SCHEMA_VALIDATION_FAILED.
  • previewUrl is the provider's image, for display while choosing; it is never stored. Keep domain.
  • The same query is answered from cache for 24 hours, whichever workspace asked it first.
  • One member may search 30 times a minute; the next search answers 429 LOGO_SEARCH_RATE_LIMITED. Keep the results you have and try again a minute later.
  • When the logo service is unavailable the route answers 503 LOGO_PROVIDER_UNAVAILABLE: try again later.
  • When GET /tenant/settings answers logosConfigured: false, every query answers [].

A query matches a record's label and its text, richtext, phone, select, multiselect and status values. Rich text matches on its words: a mention matches the name it shows, never its link. A hidden or archived attribute is never searched.

One object. POST /records/:object/search runs a full-text query with every list option on top. q is required; an empty string means "filters only". Totals are lower bounds (totalIsExact: false) because permission checks run after ranking.

curl https://api.standards.new/v1/records/contacts/search \
  -H "Authorization: Bearer $STANDARDS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "q": "lovelace", "limit": 10 }'

The whole workspace. GET /search?q= looks across every object you can read and returns hits, not records.

Query paramEffect
qThe text to search. Required.
objectsComma-separated object names to restrict to.
grouped=trueOne group per object instead of a flat list.
deletedarchived for the trash only, all for both. Default: live records.
countMode=noneSkip the exact count. Any other value keeps it.
limit, offsetFlat mode only; default 20.

A hit carries recordId, objectId, objectName, objectLabel, label, createdAt, updatedAt, and deletedAt when the record is in the trash. Flat responses are { results, total, totalIsExact, hasMore, nextOffset? }; grouped ones replace results with groups: [{ objectName, objectLabel, results, count }]: one group per object with a match you can see, the most matches first, each with its five best hits in results and every match it holds in count; total adds the counts up. Fetch the record by id when you need its values.

Next steps