Schema Discovery Endpoint
This feature is not yet generally available. It will be available in our next major release.
A metadata endpoint that lists the entity types (entityDefs) available in a domain and, for each one, the fields you can request — name, label, data type, and identity and reference information.
It is step one of the Data Feed lifecycle:
| Step | Endpoint | Purpose |
|---|---|---|
| 1. Schema Discovery (this page) | /schemas | Discover what can be loaded — entityDefs and their fields. |
| 2. Initial Data Load | /entityfeed | Bulk-load the current state of each entityDef. |
| 3. Change Feed | /changefeed | Poll for incremental changes after the initial load. |
New to the Data Feed? Start with Overview and Getting Started. It covers the Data Feed license, the API User group, obtaining a token, and the marker model — all of which this page assumes.
Use of this API requires a Data Feed license. See the Prerequisites section of Overview and Getting Started for more details.
Authenticate with an OAuth 2.0 bearer token — refer to Kahua API Authentication for provisioning credentials and requesting a token.
/schemas then has two authorization requirements beyond a valid token:
-
API User group membership. The calling account must belong to the API User group. On this endpoint a non-member receives 401, not 403 — the group is checked earlier in the pipeline here than on the feeds.
-
Data Feed license. The calling account must hold an assigned, valid kahua_DataFeed license. This is a purchased add-on and is checked per caller — every integration account needs its own seat. Missing it returns 403.
Troubleshooting a 401 from /schemas. Because "not in the API User group" also surfaces as 401 on this endpoint, a 401 here is not reliable evidence that your token is invalid. Confirm the account is in the API User group before troubleshooting the token itself.
GET https://{envName}service.kahua.com/api/v1/projects/{projectId}/apps/schemas
Authorization: Bearer <token>
Accept-Encoding: gzip, br (optional; honored if present)
If-None-Match: "<etag>" (optional; enables 304 Not Modified)
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| envName | string | Name of the environment being used. It may be empty if using production or it could be devweekly during testing. |
| projectId | integer | ID of the project where you want to retrieve app schemas. |
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| entityDefNames | string | Restrict the response to specific entityDefs. Comma-separated, each fully-qualified as App.Schema. Omit for the full domain schema. |
No paging. Schema metadata for a domain is small and bounded, so the full document comes back in one response. entityDefNames is a convenience filter, not a pagination mechanism.
This endpoint's URL differs from the two feeds: it is scoped by projectId and lives under /api/v1/..., whereas the feeds are scoped by domainKey and live under /v1/....
| Header | Value | Notes |
|---|---|---|
| ETag | "<hash>" | Content-derived. Changes whenever the schema changes — for example after an app upgrade. |
| Cache-Control | max-age=300 | You may cache the response for 5 minutes. |
Save the ETag and send it back as If-None-Match on later calls. An unchanged schema returns 304 Not Modified with no body, which is the cheap and correct way to poll for schema drift.
/schemas is not subject to the shared rate window that governs /entityfeed and /changefeed.
Status 200 OK.
| Property | Type | Description |
|---|---|---|
| projectId | long | The project ID you supplied, echoed back. |
| schemas | EntityDefSchema[] | Array of every non-excluded entity type available to you, sorted by entityDefName ascending. |
EntityDefSchema
| Property | Type | Description |
|---|---|---|
| entityDefName | string | Fully-qualified entity-definition name, e.g. kahua_AEC_RFI.RFI. This is the value you pass to /entityfeed and /changefeed. |
| appName | string | The app the entity belongs to, e.g. kahua_AEC_RFI. |
| schemaName | string | The short entity name — the last segment of entityDefName, e.g. RFI. |
| label | string | Human-readable label for the entity type. |
| templateName | string (key absent when not applicable) | Template name. Present for template-based apps; absent for older compiled apps. |
| fields | FieldInfo[] | Array of he field descriptors for this entity type. |
FieldInfo
| Property | Type | Description |
|---|---|---|
| name | string | Field name. This is the string you put in an attribute projection on /entityfeed or /changefeed. |
| label | string | Human-readable label for the field. |
| dataType | string | The field's data type. Refer to the note below on how to interpret these values. |
| isRequired | boolean | Whether a value is required for this field. |
| isReadOnly | boolean | Whether the field is read-only — for example an auto-generated number. |
| isIdentity | boolean | true when the field is a settable natural key (unique within its hierarchy). Auto-numbered keys are system-assigned and are not reported as identity. |
| linkedEntityDefName | string (key absent for scalar fields) | When dataType is Entity, the fully-qualified entityDef this reference points at. |
dataType reports Kahua's internal field-type names, emitted as-is. Expect values such as Text, Number, Boolean, Date, AutoNumber, Selector, and Entity.
These are internal type names, not a normalized or standardized type vocabulary, and the set is open-ended — new apps may introduce type names not listed here. Map them to your warehouse's types with a lookup table that has a sensible default for unrecognized values, rather than assuming a fixed, closed enumeration.
The one value with structural meaning is Entity: it marks a reference to another record. The feeds return such a field as the referenced record's numeric entity ID, and linkedEntityDefName tells you which entityDef that ID belongs to — so you can model it as a foreign key and load the target entityDef separately.
Property names are camelCase, and null values are left out of the JSON entirely rather than written as "field": null. In practice this means templateName and linkedEntityDefName simply will not appear when they have no value. Write your parser to treat a missing key as null.
Copy
{
"projectId": 1001,
"schemas": [{
"entityDefName": "kahua_AEC_RFI.RFI",
"appName": "kahua_AEC_RFI",
"schemaName": "RFI",
"label": "RFI",
"templateName": "kahua_AEC_RFI",
"fields": [{
"name": "Subject",
"label": "Subject",
"dataType": "Text",
"isRequired": true,
"isReadOnly": false,
"isIdentity": false
}, {
"name": "Number",
"label": "Number",
"dataType": "AutoNumber",
"isRequired": false,
"isReadOnly": true,
"isIdentity": false
}, {
"name": "Status",
"label": "Status",
"dataType": "Selector",
"isRequired": false,
"isReadOnly": false,
"isIdentity": false
}, {
"name": "DueDate",
"label": "Due Date",
"dataType": "Date",
"isRequired": false,
"isReadOnly": false,
"isIdentity": false
}, {
"name": "Originator",
"label": "Originator",
"dataType": "Entity",
"isRequired": false,
"isReadOnly": false,
"isIdentity": false,
"linkedEntityDefName": "kahua_PeopleManager.kahua_Contact"
}
]
}
]
}
Note that templateName and linkedEntityDefName are absent from the entries where they have no value — that is the expected shape, not a truncated example.
/schemas lists exactly the entityDefs and fields that an /entityfeed or /changefeed payload could contain — the same exclusion rules apply to all three, so discovery never promises data the feeds cannot deliver.
Not listed as entityDefs: Kahua infrastructure record types, which carry no business data, and record types belonging to internal Kahua apps.
Not listed as fields: encrypted fields, entity lock state, file attachments, image attachments, GUIDs, and raw XML — none of which carry consumer-usable values in a feed.
Not represented at all: parent/child ownership. schemas[] is a flat list. A child record type appears as its own top-level entry exactly like any other entityDef, and a parent carries no list of its children. The parent→child ownership relationship is not expressed anywhere in this response and cannot be reconstructed from it. If your warehouse model needs that hierarchy, derive it from your own knowledge of the apps involved.
Apps you cannot access are omitted. An app you cannot open at the given projectId is left out of the full-domain response, and returns 404 if you name it explicitly — so this endpoint cannot be used to enumerate apps you have no rights to.
Response Body
| Property | Type | Description |
|---|---|---|
| errorCode | string | Code that represents the error condition |
| message | string | Additional information about the error |
Codes
| Status | errorCode | When |
|---|---|---|
| 400 | 400 | Malformed entityDefNames — a value that isn't a fully-qualified App.Schema name. Validated before authorization, so this response only ever echoes your own input. |
| 401 | — | Missing, invalid, or expired bearer token; or the caller is not in the API User group. |
| 403 | 403 | Authenticated, but the identity has no access to the domain, or does not hold a Data Feed license (kahua_DataFeed). |
| 404 | 404 | A requested entityDefNames value does not exist, is excluded, or belongs to an app you cannot access. |
Full domain schema
curl -X GET 'https://service.kahua.com/api/v1/projects/1001/apps/schemas' \
-H 'Authorization: Bearer <token>' \
-H 'Accept-Encoding: gzip, br'
Only specific entityDefs
curl -X GET 'https://service.kahua.com/api/v1/projects/1001/apps/schemas?entityDefNames=kahua_AEC_RFI.RFI,kahua_AEC_DailyReport.DailyReport' \
-H 'Authorization: Bearer <token>'
Conditional revalidation — check for schema drift cheaply
curl -X GET 'https://service.kahua.com/api/v1/projects/1001/apps/schemas' \
-H 'Authorization: Bearer <token>' \
-H 'If-None-Match: "b3Jpb2xl…="'
# → 304 Not Modified (schema unchanged; no body returned)
-
From the response, pick the entityDefName values your warehouse needs.
-
For each, pick the name values from its fields[] — that list becomes your attribute projection.
-
Note any linkedEntityDefName targets: if you want details of a referenced record rather than just its ID, add that entityDef to your load list too.
-
Proceed to /entityfeed for the initial load.
Save the ETag. Re-checking it with If-None-Match after an app upgrade is the supported way to detect that your projections need revisiting.
-
Data Feed — Overview and Getting Started
-
Entity Feed — /entityfeed
-
Change Feed — /changefeed