Create records through the API
Create a record for an object through the Henceforth data API and confirm it was written.
Prerequisites
Section titled “Prerequisites”- 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,
coreandAccount).
Routes
Section titled “Routes”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 |
1. Send the create request
Section titled “1. Send the create request”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}2. Read the response
Section titled “2. Read the response”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.
Verification
Section titled “Verification”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.
Validation
Section titled “Validation”On every create and update, the runtime validates the request body against the compiled metadata:
- Unknown fields are rejected –
UNKNOWN_FIELD(422) for any field name not defined in the metadata. - System fields (
id,createdAt,updatedAt) are read-only and rejected if present. - Formula fields are read-only and rejected if present.
- Rollup fields are read-only and rejected if present.
- Type checking – each field value must match the expected JSON type for its scalar type (strings for
text, booleans forbool, integers forint, and so on). - Required fields must be present and non-null on create. On update, required fields cannot be set to null.
- Lookup values must be valid UUID strings referencing a record that exists in
hf_object. For multi-target lookups, the referenced record’sobject_typemust be in the allowed set. - Abstract objects cannot be instantiated.
- Custom rules – object-level
rules:block expressions (declared in*_object.json5) are evaluated against the record. A rule that fails produces aRULE_FAILEDfield error, attributed to a pinned field, a single referenced field, or the whole record when the expression spans more than one field.
Error responses
Section titled “Error responses”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.
See also
Section titled “See also”- Objects, Records, and Fields – what an object, record, and field are, how a create request becomes rows in the database, and why creates and updates validate against metadata
- Read, update, and delete records through the API
- Upsert records through the API
- FieldDefinition – full field type and validation reference