Field Types & Validation
The type and format each field expects, what the API accepts, and how validation errors are reported.
Every write endpoint type-checks its request body before anything is saved. A field with the wrong type is rejected with 400 and a message naming the field, what it expected, and what you actually sent - the whole request fails, so you never end up with a half-written record.
{
"error": "Invalid request body",
"details": [
"'track' must be a whole number (received the string \"Main Stage\").",
"'date' is not a real calendar date (received the string \"2026-02-30\")."
]
}
Fields to check before your first write
Most fields are plain text. These few take a number or a particular format, so they are worth confirming when you build your payload.
| Field | Object | Type | What it actually is |
|---|---|---|---|
track |
session | integer | The position of the track in your event’s track list, counting from 0. Put the stage or room name in location. |
tintColor |
session | integer | The session colour. Send a hex string such as "#3366FF", or a 32-bit ARGB integer if you already have one (4294967295 is white). |
tier |
sponsor | integer | The position of the tier in your event’s sponsor-tier list, counting from 0. Tier names are set under Event Settings. |
tags |
session, speaker | string | One tag, or several separated by commas. An array is accepted too. |
representativeEmails |
sponsor | string | One address, or several separated by commas. An array is accepted too. |
ticketRedemption |
session | string | The ticket tier that can be redeemed at this session. Leave empty for none. |
options |
poll | array of strings | A real JSON array, at least 2 entries - not comma-separated, because an option may itself contain a comma. |
maxChoices |
poll | integer | How many options one attendee may pick. Never more than the number of options. |
status |
question | enum | Exactly one of pending, approved, rejected, answered. Replaces the stored approved/rejected/answered booleans. |
points, bonusPoints |
score object | number | Decimals allowed; rounded when the leaderboard is calculated. |
trackArray, tierArray |
event | array of strings | The track and tier names. track and tier above are positions in these lists. |
Accepted formats
The API is deliberately forgiving where the intent is unambiguous, so you rarely have to match the internal storage format exactly.
| Type | Accepted | Stored as |
|---|---|---|
| integer | 150, "150" |
150 - decimals and non-numeric strings are rejected |
| boolean | true, "true", "yes", 1 / false, "false", "no", 0 |
true / false |
| colour | 4281558783, "#3366FF", "#FF3366FF", "0xFF3366FF" |
ARGB integer - a 6-digit hex gets a fully opaque alpha. A fully transparent colour is corrected to the field’s clearsTo value, with a warning |
| colour list | ["#3366FF", "#43A047"] |
An array of ARGB integers, matched position by position to the list it belongs to |
| tag list | "ai, keynote" or ["ai", "keynote"] |
"ai, keynote" - trimmed, blanks dropped, duplicates removed |
| email list | "[email protected], [email protected]" or ["[email protected]", "[email protected]"] |
"[email protected],[email protected]" - each address is validated |
" [email protected] " |
"[email protected]" - trimmed and lowercased |
|
| date | "2026-09-14" |
Same. Must be a real calendar date; "" clears it |
| time | "09:00", "9:00" |
"09:00" - 24-hour; "" clears it |
| rich text | Delta JSON string, or plain text | Same - malformed Delta JSON is rejected. See Rich Text Format |
| number | 10, 2.5, "2.5" |
2.5 - unlike integer fields, decimals are kept |
| string array | ["Yes", "No"] |
Same - entries are trimmed; blanks and duplicates are rejected |
| enum | "answered", "Answered" |
The canonical spelling. An invalid value is rejected with the full list of valid ones |
A few more rules worth knowing:
- Send
documentIdto edit; leave it out to create. One field names the record you are writing, on every object type, and a read gives it straight back. Omit it and you have asked for a new record, which succeeds with200- so checkcreatedin the response, or dry-run first and read thechangesit reports. documentIdis this record; a<type>Idpoints at a different one. That is why/upsertQuestiontakes asessionIdas well - the question, and the session it belongs to. The older per-type spelling of a record’s own id (sessionIdon a session,speakerIdon a speaker) still works on every endpoint and is not going away;/apiSchemalists it asidentifiedByAliases. Send one or the other - two different values is a400rather than a guess.- Omitted means unchanged. On an edit, only the fields you send are written. To clear a field send
""for text,falsefor a flag, or0for a number - notnull, which is rejected. - Colours are the exception to that.
0is a valid colour: transparent black. It is not “no colour”, and a client will happily paint it over whatever should have shown through. Clear a colour with itsclearsTovalue from/apiSchema(#FFFFFFFF) instead. Send a transparent colour anyway and it is corrected toclearsTo, with a warning saying so. - The body must be a JSON object. Send
Content-Type: application/json. A bare string or array is rejected with400. - Required fields are enforced on create. Creating a speaker without a
name, or a poll withoutoptions, is rejected with400before anything is written. On an edit the same fields are optional - but sending one empty is still rejected, since that would wipe it. - Writes are
POST, reads areGET. The wrong verb returns405with anAllowheader. - Test before you write. Add
?dryRun=1to any write to validate it - reference checks included - without saving. See Discovery & Dry Runs.
Warnings and ignored fields
Not everything that looks wrong is fatal. When a write succeeds but something is worth a second look, the 200 response carries extra keys:
| Key | Meaning |
|---|---|
warnings |
The value was saved, but probably isn’t what you meant - a link that isn’t an absolute http(s) URL, or a timeEnd before its timeStart. |
ignoredFields |
Body keys the endpoint does not recognise at all, so they were dropped. Near-misses get a suggestion, e.g. "unlisted (did you mean 'unListed'?)". |
{
"status": "success",
"sessionId": "aB3xY...",
"created": true,
"warnings": ["'handOutLink' does not look like an absolute http(s) URL (\"www.example.com/deck.pdf\"); it may not open as a link in the app or website widgets."],
"ignoredFields": ["colour", "unlisted (did you mean 'unListed'?)"]
}
Both keys are omitted entirely when there is nothing to report, so a clean request gets the same response shape it always did. ignoredFields is the quickest way to catch a typo, or a payload built against a different system.
Protected fields
A few fields would be a lie to accept. Attendee poll votes, Q&A upvotes and question scores are written by the app; billing limits and tenancy are not yours to set; and a question’s approved/rejected/answered flags have a supported replacement in status. Dropping any of those quietly would be worse than useless - a caller sending votes would get a 200 and reasonably believe it had recorded them.
So they are rejected with a 400 that names the reason and, where there is one, the field to use instead:
{
"error": "Invalid request body",
"details": [
"'votes' cannot be written through the API - attendee ballots are cast in the app and would be overwritten. Read results with /getPollResults.",
"'approved' cannot be written through the API - moderation state is set with the 'status' field (pending | approved | rejected | answered)."
]
}
This is the difference between an unrecognised key, which lands in ignoredFields, and a recognised but forbidden one, which fails the request outright.
Read-modify-write still works
The protected list is deliberately narrow, and no field that a read hands back to you is on it. Server-owned bookkeeping - clientId, deleted, hidden, apiImported, updated - and derived values like a session’s speakers array are simply reported in ignoredFields. So fetching an object, changing one value, and posting the whole thing back is safe: the fields you didn’t mean to write are listed, not fatal.
The one field in that payload that is not ignored is documentId: it is read as the record you want to edit. That is what makes the round trip an edit rather than a duplicate.
Anything genuinely protected is either never returned at all (poll votes, question voters) or has an obvious supported alternative, so a round trip can’t trip over one.
Every object’s protected fields, with their reasons, are listed by GET /apiSchema.
Cloudflare vs. the API
api.event-vault.com sits behind Cloudflare, which rejects unrecognised user agents before the request reaches the API. Always send a normal User-Agent header.
Every error the API itself returns is JSON - {"error": ..., "details": [...]}, with a code and, where it applies, a named field such as missingScope. So the content type identifies who answered: a 403 with a JSON body is a genuine permission or scope failure from the API, and retrying will not change it. A 403 with an HTML body, or one mentioning error code: 1010, never reached the API at all - that is Cloudflare, and a normal User-Agent fixes it.