Schema Discovery & Dry Runs
Ask the API what it accepts, check what a key can do, and validate a write without saving it.
Three features exist so an integration - or an AI assistant - never has to guess: it can ask what the API accepts, ask what its own key is allowed to do, and try a write without saving it.
GET /apiSchema
Returns a machine-readable description of every writable field on every object type. Any valid credential works; no particular scope is required.
The field list is generated from the very rules the server validates against, so it cannot drift the way a hand-written specification does. If the API accepts a field, it is in here; if it is in here, that is the type actually enforced.
It is a complete reference on its own. Alongside the fields it returns:
fieldTypes- what every type name means, and what each one accepts.formats- the rich-text and content-section structures, which a type name alone cannot describe.conventions- how omitted fields, dry runs, errors and rate limits behave.thisEvent- your event’s actual track and tier names, and the range of numbers that are valid for them.endpointsandyourScopes- what exists, and what this key may call.
This is why the AI assistant briefing is so short: rather than pasting a field list into every conversation, it tells the assistant to fetch this.
{
"baseUrl": "https://api.event-vault.com",
"requiredHeaders": { "x-api-key": "...", "x-client-id": "...", "x-event-id": "...", "x-timestamp": "..." },
"conventions": {
"dryRun": "Add ?dryRun=1 to any write to validate the request ...",
"omittedFields": "Omitted fields are left unchanged ...",
"protectedFields": "Keys listed under an object's protectedFields are rejected with a 400 ..."
},
"thisEvent": {
"trackArray": ["Main Stage", "Workshops"],
"trackIndexRange": "0..1",
"tierArray": ["Gold", "Silver"],
"tierIndexRange": "0..1"
},
"yourScopes": ["sessions:read", "sessions:write"],
"endpoints": [ { "path": "/upsertSession", "method": "POST", "scope": "sessions:write", "object": "sessions" } ],
"objects": {
"sessions": {
"fields": {
"name": { "type": "string", "required": true, "maxLength": 300, "description": "Session title." },
"track": { "type": "int", "min": 0, "max": 1, "description": "Index into the event's trackArray ..." }
},
"protectedFields": {
"speakers": "it is derived from the speaker sections in 'contentData' - add a speaker section instead."
}
}
}
}
Note thisEvent: the track and tier ranges are reported for your event, so you know what track values are legal before you send one.
GET /whoami
Describes the credential you are using. Any valid credential works; no particular scope is required.
Useful as a first call: it confirms connectivity and lists the exact permissions, so an integration never has to discover its own limits by collecting 403s.
{
"auth": "scoped",
"clientId": "acme",
"eventId": "ev123",
"eventName": "Acme Conference 2026",
"scopes": ["sessions:read", "sessions:write"],
"scopeDescriptions": { "sessions:write": "Create, edit and delete sessions." },
"tokenId": "tok_abc",
"name": "Programme importer",
"boundEventId": "ev123",
"expiresAt": "2026-09-01T12:00:00.000Z",
"rateLimit": { "requests": 300, "windowSeconds": 60, "scope": "per token" }
}
A legacy client-wide key reports "auth": "legacy" and a note recommending a scoped token instead.
?dryRun=1
Add dryRun=1 to the query string - or "dryRun": true to the body - of any write endpoint to validate the request without writing anything.
A dry run is not just a type check. It runs the same path the real write would: referenced ids are looked up, cross-field rules are applied, and the warnings you would have received come back too.
curl -X POST "https://api.event-vault.com/upsertPoll?dryRun=1" \
-H "x-api-key: $EV_API_KEY" \
-H "x-client-id: $EV_CLIENT_ID" \
-H "x-event-id: $EV_EVENT_ID" \
-H "x-timestamp: $(python3 -c 'import time;print(int(time.time()*1000))')" \
-H "Content-Type: application/json" \
-d '{"sessionId":"s42","question":"Ready?","options":["Yes","No"]}'
{
"status": "success",
"dryRun": true,
"message": "Validation passed. Nothing was written because dryRun was set.",
"created": true,
"warnings": ["the session 's42' has hasPoll set to false, so this poll will not be reachable in the app until that is enabled (set it with /upsertSession)."]
}
A failing dry run returns the same 400 the real request would have. Destructive endpoints use it to report the damage in advance - deletePoll tells you how many votes would be destroyed, and deleteScoreObject how many attendees would lose points.