Documentation

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.
  • endpoints and yourScopes - 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.