Skip to content

Create records through the API

Create a record for an object through the Henceforth data API and confirm it was written.

  • The target object and its fields are already applied to the database. See From Metadata to Database.
  • You know the object’s module key and object key (for example, core and Account).

All data API routes are derived from the module key and object key:

Method Path Operation
POST /data/{moduleKey}/{objectKey} Create a record
GET /data/{moduleKey}/{objectKey}/{id} Read a record by ID
PATCH /data/{moduleKey}/{objectKey}/{id} Update a record (partial)
DELETE /data/{moduleKey}/{objectKey}/{id} Delete a record
PUT /data/{moduleKey}/{objectKey}/{fieldKey} Upsert on a unique field
POST /data/query Execute a HOQS query

POST /data/core/Account

Request body — every field you set must be defined on the object, and system fields (id, createdAt, updatedAt, objectType) must be omitted:

{
"name": "Acme Corp",
"industry": "Technology",
"active": true
}

A successful create returns 201 Created with the full record, including the system fields the runtime generated:

{
"id": "019502a4-b4e7-7f3a-8c1e-0a1b2c3d4e5f",
"objectType": "core.Account",
"createdAt": "2024-03-15T10:30:00.000Z",
"updatedAt": "2024-03-15T10:30:00.000Z",
"name": "Acme Corp",
"industry": "Technology",
"active": true
}

The response also includes a Location header with the URL of the created record.

3. For inherited objects, expect writes across the inheritance chain

Section titled “3. For inherited objects, expect writes across the inheritance chain”

If the object extends another object, the runtime inserts into every table in the chain, base to leaf, sharing one UUID. Creating an Individual record inserts into hf.object, then core.party, then core.individual. Abstract objects cannot be created directly — the API returns ABSTRACT_OBJECT (422) if you try. See Objects, Records, and Fields for the non-inherited case and why the record is read back through the view before it’s returned.

GET the Location header URL (or GET /data/core/Account/{id} using the id from the response) and confirm the record comes back with the values you sent. See Read, update, and delete records through the API.

On every create and update, the runtime validates the request body against the compiled metadata:

  1. Unknown fields are rejected – UNKNOWN_FIELD (422) for any field name not defined in the metadata.
  2. System fields (id, createdAt, updatedAt) are read-only and rejected if present.
  3. Formula fields are read-only and rejected if present.
  4. Rollup fields are read-only and rejected if present.
  5. Type checking – each field value must match the expected JSON type for its scalar type (strings for text, booleans for bool, integers for int, and so on).
  6. Required fields must be present and non-null on create. On update, required fields cannot be set to null.
  7. Lookup values must be valid UUID strings referencing a record that exists in hf_object. For multi-target lookups, the referenced record’s object_type must be in the allowed set.
  8. Abstract objects cannot be instantiated.
  9. Custom rules – object-level rules: block expressions (declared in *_object.json5) are evaluated against the record. A rule that fails produces a RULE_FAILED field error, attributed to a pinned field, a single referenced field, or the whole record when the expression spans more than one field.

All errors follow a consistent JSON structure:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"fields": [
{
"field": "email",
"code": "REQUIRED",
"message": "Field 'email' is required"
}
]
}
}
Error code HTTP status Description
OBJECT_NOT_FOUND 404 The module/object combination does not exist in the metadata.
RECORD_NOT_FOUND 404 No record exists with the specified ID.
INVALID_ID 400 The ID in the URL path is not a valid UUID.
ABSTRACT_OBJECT 422 The object is abstract and cannot be instantiated.
VALIDATION_ERROR 422 One or more field values failed validation. The fields array contains per-field details.
UNKNOWN_FIELD 422 The request body contains a field name not defined in the metadata.
UNIQUE_CONSTRAINT_VIOLATION 409 A unique constraint was violated (e.g., duplicate email).
FIELD_NOT_UNIQUE 400 The field specified for upsert is not marked unique.
INTERNAL_ERROR 500 An unexpected server error.
DECODE_ERROR 500 A column value could not be decoded to its expected type (schema drift or database fault).
REF_MODULE_NOT_READY 503 The currency registry has not been populated yet (the reference module has not been applied or its apply is still in progress). Retry after the apply completes.

Other How-to pages in this section link back to this table rather than repeating it.