# Getting started This guide covers the essentials to understand before making your first API request. Version 2 is the current API release. You can find the legacy Version 1 documentation in the version selector or at [Version 1](https://docs.onderwijsregio.onderwijsin.nl/version-1/getting-started). ::note Feedback is welcome. Email with questions or suggestions. :: The API is part of a [shared digital infrastructure](https://onderwijsin.nl/projecten/digitale-infrastructuur-onderwijsregios){rel=""nofollow""}, developed by and for education regions (*onderwijsregio’s*). It supports prospective teachers and education professionals from orientation through their start in education. The underlying [Airtable](https://airtable.com){rel=""nofollow""} system acts as an applicant tracking system (ATS) and CRM for regional coordination, data sharing, and reporting. The API provides a secure, predictable integration layer for external systems. This documentation is intended for developers building or maintaining integrations with the platform. ## Access and authentication Access to the API is granted via an API key. - API keys are issued only to participating education regions. - Each region can create and manage multiple API keys. - Send every key in the `x-api-key` request header. ::card --- icon: i-lucide-key-round title: Authenticate requests to: https://docs.onderwijsregio.onderwijsin.nl/authentication/requests --- Request an initial key and send it securely with each API request. :: ## Discover your schema Before creating or updating a candidate, you should retrieve the schema for your region using the `/schema` endpoint. This endpoint returns the **authoritative, region-specific definition** of all fields that are accepted in the candidate payload. If a payload validates against the schema, it will validate against the API. ::card --- icon: i-lucide-brackets title: Read the schema guide to: https://docs.onderwijsregio.onderwijsin.nl/getting-started/schema --- Review the shared data model and the custom fields for your region. :: ## Asynchronous by design All write operations (create, update, delete) are handled asynchronously by the **Mutation Engine**. - Successful write requests return `202 Accepted`. - A `202` response means the mutation was validated and queued. - Execution happens later; it is not immediate. Integrations must be designed with asynchronous behavior in mind. ::card --- icon: i-lucide-git-pull-request-arrow title: Understand the Mutation Engine to: https://docs.onderwijsregio.onderwijsin.nl/mutations/mutation-engine --- Learn how queued mutations progress and how to handle their responses. :: ## Idempotency and callbacks Mutation endpoints support: - `idempotencyKey` for safe retries. - `callbackUrl` for completion and failure notifications. These features are strongly recommended for reliable integrations. ::card --- icon: i-lucide-shield-check title: Verify callback signatures to: https://docs.onderwijsregio.onderwijsin.nl/mutations/callback-signatures --- Confirm that callback requests were sent by the Mutation Engine. :: ## Sandbox mode Most endpoints support sandbox mode using the `x-api-sandbox: true` header. - Requests are fully validated and processed. - Production data is never modified. Use sandbox mode during development and testing. # Sandbox environment You can test your integration in the sandbox environment before moving to production. The **sandbox environment** is available for all non-production use cases. It provides access to the same API features as production, while being fully isolated from production data. This allows you to develop and test safely without polluting your region’s live data. ::note Use the same API keys in the sandbox and in production. :: ::callout{color="warning" icon="i-lucide-triangle-alert"} The sandbox is still under active development. Its API surface is stable, but you may encounter unexpected errors. :: ## Supported routes The sandbox environment currently supports all routes under `/api/v2/candidates`. If the `x-api-sandbox` header is set for other routes, it will be ignored. Confirm sandbox mode through the `meta` property in the API response: ```json [response.json] { "statusCode": 200, "message": "Successfully retrieved ...", "data": { ... }, "meta": { "sandboxEnabled": true } } ``` ## Enable the sandbox You can enable the sandbox in one of two ways: ### Use the `/sandbox` endpoint prefix Replace `/v2` with `/sandbox` in the API endpoint. ```text https://onderwijsregio.onderwijsin.nl/api/sandbox/schema ``` ### Set a request header Include the following header in your request: ```http x-api-sandbox: true ``` ## Understand data scope and persistence Sandbox data is **scoped to your region**. This means that any API key belonging to the same region has access to the same sandbox data. To prevent an excessive buildup of test data, the sandbox is **cleared automatically once per day**. As a result, any test data you create should be considered temporary and may be gone the next day. ## Know the differences from production Almost all production rules, conventions, and constraints—including [rate limits](https://docs.onderwijsregio.onderwijsin.nl/misc/troubleshooting)—also apply in the sandbox. The primary goal of the sandbox is to offer an environment that behaves as closely as possible to production, while keeping your region’s production data clean and unaffected. ## Verify sandbox requests You can confirm that a request was executed against the **sandbox environment** in two ways: ::steps ### Inspect API response metadata The response includes `meta.sandboxEnabled`. ### Inspect the callback request header Callbacks sent to your `callbackUrl` include `x-mutationengine-sandbox-enabled`. A value of `true` confirms sandbox processing. :: # Schema The schema endpoint provides the authoritative definition of the candidate request payload for your education region. It describes *exactly* which fields are accepted when creating or updating a candidate—and how those fields are validated by the API. Unlike earlier versions, the API returns a **standards-compliant JSON Schema**. It is directly compatible with OpenAPI and mirrors runtime validation one-to-one. This is made possible by [Zod’s native JSON Schema support](https://zod.dev/json-schema){rel=""nofollow""}. ## Why this schema matters The `/schema` endpoint returns a schema that: - **Directly mirrors the API data structure.** - **Uses the same validation rules enforced by the API.** - **Is fully OpenAPI- and JSON Schema-compatible.** - **Includes region-specific custom fields.** - **Differentiates between create and update operations.** There is no translation layer, no duplication, and no drift between documentation and runtime behavior. If a payload validates against the returned schema, it will validate against the API. ## When to use the `/schema` endpoint Before creating or updating a candidate, you should retrieve the schema for your region using the `/schema` endpoint. You should always base payload construction and validation on the response of this endpoint, especially when working with: - Required vs optional fields - Enumerated values - Arrays and nested objects - Region-specific custom fields ::note The first `/schema` request for a region can take **3–5 seconds** while the system inspects the region-specific schema. Later requests use a **cached response** and are typically much faster. :: ## Create vs update schemas The schema endpoint supports both candidate operations: - **Create schema**:br Describes the full payload required to create a new candidate, including required fields. - **Update schema**:br Describes a partial payload for updating an existing candidate. Required fields are relaxed to reflect patch semantics. You can select the operation using the `operation` query parameter: ```http GET /schema?operation=create GET /schema?operation=update ``` Each operation returns a schema that exactly matches the validation rules applied by the corresponding `/candidates` endpoint. ## JSON Schema targets By default, the schema is generated for **OpenAPI 3.0 compatibility**, making it ideal for API tooling, SDKs, and documentation generators such as Scalar. You can optionally request a different JSON Schema dialect using the `target` query parameter: ```http GET /schema?target=openapi-3.0 (default) GET /schema?target=draft-2020-12 GET /schema?target=draft-07 GET /schema?target=draft-04 ``` Supported targets correspond to [valid JSON Schema specifications](https://json-schema.org/specification){rel=""nofollow""}. This allows you to integrate the schema seamlessly with different validators, form generators, or AI structured-output tooling. ## What the schema defines The returned schema is a **complete JSON Schema document** and defines: - Which fields are available - Which fields are required - Which values are allowed - How arrays, nested objects, and unions are structured - Whether additional properties are permitted At the top level, the schema explicitly defines: - `type` - `properties` - `required` - `additionalProperties` This makes the schema immediately usable with standard JSON Schema validators. ## Schema structure The schema returned by `/schema` is scoped to the authenticated region and covers the **entire candidate payload**, consisting of three conceptual parts: ### Shared fields Shared fields represent the standardized candidate data model used across all education regions. Examples include: - Identity and contact information - Privacy consent - Candidate phase - Sector, role, and subject preferences - Qualifications and motivation - Attachments and documents Shared fields are always present in the schema and follow consistent validation rules across regions. ### Interactions Interactions represent contact moments or events related to a candidate, such as phone calls, emails, or meetings. In the schema, interactions are modeled as an **array of structured objects**, each defining: - Required properties (such as interaction type and date) - Allowed interaction types - Optional descriptive fields (summary, notes, duration) Interactions can be included when creating or updating a candidate, or managed separately through the dedicated interaction endpoints. ### Custom fields Custom fields are **region-specific extensions** to the shared candidate schema. Key characteristics: - Returned as a nested schema under `customFields` - Dynamically generated per region - Structure, data types, and allowed values may differ between regions - Fully described using standard JSON Schema constructs Custom fields are defined in the same way as shared fields, including: - Required vs optional - Data type - Enumerated values (if applicable) - Descriptions Your integration must treat the custom fields schema as **dynamic** and must not assume a fixed or global set of custom fields. ### Review an example schema Below is an example schema returned by the `/schema` endpoint for a `create` operation, in the `draft-2020-12` target. ::code-collapse ```json [schema.json] { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "name": { "title": "Candidate name", "description": "The full name of the candidate.", "examples": ["Jan Jansen"], "type": "string", "minLength": 1 }, "email": { "title": "Candidate email", "description": "The primary email address of the candidate.", "examples": ["jan.jansen@email.com"], "type": "string", "format": "email", "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" }, "privacyConsent": { "title": "Privacy consent", "description": "Indicates whether the candidate has given consent for data processing.", "examples": [true], "type": "boolean" }, "phone": { "title": "Candidate phone", "description": "The primary phone number of the candidate.", "examples": ["+31612345678"], "type": "string", "minLength": 5 }, "linkedin": { "title": "LinkedIn profile", "description": "The LinkedIn profile URL of the candidate.", "examples": ["https://www.linkedin.com/in/example"], "type": "string", "format": "uri" }, "dateOfBirth": { "title": "Date of birth", "description": "The date of birth of the candidate.", "examples": ["1995-06-01"], "type": "string", "format": "date", "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$" }, "address": { "title": "Address", "description": "The residential address of the candidate.", "examples": ["Albert Cuypstraat 123"], "type": "string" }, "city": { "title": "City", "description": "The city where the candidate resides.", "examples": ["Amsterdam"], "type": "string" }, "postalCode": { "title": "Postal code", "description": "The postal code of the candidate's address.", "examples": ["1011AB", "1234 CD"], "type": "string" }, "phase": { "title": "Candidate phase", "description": "Phase the candidate is currently in.", "examples": ["nieuw", "oriënteren", "in opleiding"], "default": "nieuw", "type": "string", "enum": [ "nieuw", "oriënteren", "in opleiding", "matchen", "voorbereiden", "werkzaam", "uitgestroomd" ] }, "sectorPreferences": { "title": "Sector preferences", "description": "Sector(s) that the candidate is interested in.", "examples": [["Primair onderwijs", "Voortgezet onderwijs", "Speciaal onderwijs"]], "type": "array", "items": { "type": "string", "enum": [ "Primair onderwijs", "Voortgezet onderwijs", "Speciaal onderwijs", "Middelbaar beroepsonderwijs", "Hoger onderwijs", "Praktijkonderwijs" ] } }, "rolePreferences": { "title": "Role preferences", "description": "Role(s), jobs and/or positions that the candidate is interested in.", "examples": [["Docent / leraar", "Instructeur (mbo)", "OOP"]], "type": "array", "items": { "type": "string", "enum": [ "Docent / leraar", "Instructeur (mbo)", "OOP", "Ondersteuning", "Leerlingenzorg", "Midden management", "Schoolleiding", "Anders" ] } }, "desiredQualifications": { "title": "Desired qualifications", "description": "Qualification(s) that the candidate wants to obtain.", "examples": [["Primair onderwijs", "Beperkt tweedegraads", "Tweedegraads"]], "type": "array", "items": { "type": "string", "enum": [ "Primair onderwijs", "Beperkt tweedegraads", "Tweedegraads", "Eerstegraads", "PDG", "BKO/BDB", "Kwalificatie onderwijs ondersteunend personeel", "Kwalificatie Instructeur (MBO)", "Geen: gastdocentschap", "Pabo", "Onbekend", "Nog geen idee", "Niet van toepassing" ] } }, "priorEducation": { "title": "Prior education", "description": "The highest level of education the candidate has completed.", "examples": ["geen", "praktijkonderwijs", "vmbo-bl"], "type": "string", "enum": [ "geen", "praktijkonderwijs", "vmbo-bl", "vmbo-kl", "vmbo-gl", "vmbo-tl", "vmbo", "havo", "vwo", "mbo entree", "mbo 2", "mbo 3", "mbo 4", "mbo", "associate degree", "hbo propedeuse", "hbo bachelor", "hbo master", "hbo", "wo bachelor", "wo master", "wo", "PhD" ] }, "subjectPreferences": { "title": "Subject preferences", "description": "Subject(s) that the candidate is interested in. Relevant for sector \"Voortgezet onderwijs\".", "examples": [["Nederlands", "Engels", "Wiskunde"]], "type": "array", "items": { "type": "string", "enum": [ "Nederlands", "Engels", "Wiskunde", "Wiskunde A", "Wiskunde B", "Wiskunde C", "Wiskunde D", "Frans", "Duits", "Spaans", "Latijnse taal en cultuur", "Griekse taal en cultuur", "Klassieke talen", "KCV", "Geschiedenis", "Aardrijkskunde", "Maatschappijleer", "Maatschappijkunde", "Maatschappijwetenschappen", "Filosofie", "Levensbeschouwing", "Economie", "Algemene economie", "Bedrijfseconomie", "Ondernemerschap - Academische vaardigheden", "Natuurkunde", "Scheikunde", "Biologie", "Rekenen", "NLT (Natuur, leven en techniek)", "Algemene Natuurwetenschappen", "NaSk", "Science", "Life sciences", "Informatica", "Techniek", "Technologie en toepassing", "Toepassingsgericht onderwijs", "Programmeren", "installeren en energie", "Produceren", "installeren", "energie (PIE)", "Art en Technologie", "Digital Creative Technology (DCT)", "Digital Technology", "Digitaal burgerschap", "STEAM", "T&T", "CKV", "Kunst & Techniek", "Kunst: algemeen", "Kunst: beeldend", "Kunst: beeldend / tekenen", "Kunst: dans", "Kunst: drama", "Kunst: podium", "Kunst: tekenen", "Kunst: theater", "Kunstgeschiedenis/CKV", "Handvaardigheid", "Muziek", "Vormgeving en media", "Lichamelijke opvoeding", "Lichamelijke opvoeding 2", "Bewegen sport en maatschappij (BSM)", "Dienstverlening en producten", "Praktijkgericht programma (havo)", "Praktijkprogramma zorg (mavo)", "Zorg en welzijn", "Mens en dienstverlening", "Mens en Maatschappij", "Mens en Natuur", "Onderzoeken & Ontwerp", "Onderzoekend leren", "Onderzoeksvaardigheden", "Millennium Skills", "Global perspectives", "Global citizenship", "Big History", "International Baccalaureate", "Psychologie", "Persoonlijke Vorming", "PGP maatschappij", "PGP P&O", "PGP technologie", "LOB/PWS", "PWS", "Zomer PWS", "Leefstijl", "Kennis van het geestelijk leven", "Vrede en Recht", "Wetenschap", "Wetenschapsoriëntatie", "Humaniora", "Arabisch", "Chinees", "Italiaans", "Russisch", "NT2", "Econasium", "E&O" ] } }, "motivation": { "title": "Motivation", "description": "Motivation letter of the candidate.", "examples": ["I am very motivated to start this program."], "type": "string" }, "curriculumVitae": { "title": "Curriculum vitae", "description": "List of attachments representing the candidate's CV.", "examples": [ [ { "filename": "cv_jan_jansen.pdf", "url": "https://cdn.example.com/cv_jan_jansen.pdf" }, { "filename": "cv_image_jan_jansen.png", "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..." } ] ], "type": "array", "items": { "title": "Attachment payload", "description": "Payload representing an attachment provided either by URL or base64-encoded data.", "anyOf": [ { "type": "object", "properties": { "filename": { "title": "Attachment filename", "description": "The name of the file including its extension.", "examples": ["document.pdf", "image.png"], "type": "string", "minLength": 1 }, "url": { "title": "Attachment URL", "description": "Public URL where the file can be accessed.", "examples": ["https://cdn.example.com/file.png"], "type": "string", "format": "uri" } }, "required": ["filename", "url"], "additionalProperties": false }, { "type": "object", "properties": { "filename": { "title": "Attachment filename", "description": "The name of the file including its extension.", "examples": ["document.pdf", "image.png"], "type": "string", "minLength": 1 }, "data": { "title": "Base64 attachment data", "description": "Base64-encoded file contents including the data URI prefix.", "examples": ["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."], "type": "string", "minLength": 1 } }, "required": ["filename", "data"], "additionalProperties": false } ] } }, "otherAttachments": { "title": "Other attachments", "description": "List of other attachments and documents provided by the candidate.", "examples": [ [ { "filename": "portfolio_jan_jansen.pdf", "url": "https://cdn.example.com/portfolio_jan_jansen.pdf" }, { "filename": "avatar_jan_jansen.png", "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..." } ] ], "type": "array", "items": { "title": "Attachment payload", "description": "Payload representing an attachment provided either by URL or base64-encoded data.", "anyOf": [ { "type": "object", "properties": { "filename": { "title": "Attachment filename", "description": "The name of the file including its extension.", "examples": ["document.pdf", "image.png"], "type": "string", "minLength": 1 }, "url": { "title": "Attachment URL", "description": "Public URL where the file can be accessed.", "examples": ["https://cdn.example.com/file.png"], "type": "string", "format": "uri" } }, "required": ["filename", "url"], "additionalProperties": false }, { "type": "object", "properties": { "filename": { "title": "Attachment filename", "description": "The name of the file including its extension.", "examples": ["document.pdf", "image.png"], "type": "string", "minLength": 1 }, "data": { "title": "Base64 attachment data", "description": "Base64-encoded file contents including the data URI prefix.", "examples": ["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."], "type": "string", "minLength": 1 } }, "required": ["filename", "data"], "additionalProperties": false } ] } }, "interactions": { "title": "Interactions", "description": "List of interactions with the candidate.", "examples": [ [ { "type": "Telefonisch", "performedAt": "2024-01-01T10:00:00.000Z", "summary": "Initial intake call", "notes": "Candidate was enthusiastic", "duration": 1800 }, { "type": "E-mail", "performedAt": "2024-01-15T14:30:00.000Z" } ] ], "type": "array", "items": { "title": "Interaction", "description": "Schema for a single interaction with a candidate.", "type": "object", "properties": { "id": { "title": "Airtable record ID", "description": "Unique identifier of an Airtable record.", "examples": ["recABC123DEF456GH"], "type": "string", "pattern": "^rec[a-zA-Z0-9]{14}$" }, "type": { "title": "Interaction type", "description": "Type of interaction associated with a candidate.", "examples": ["Telefonisch", "E-mail", "Inschrijving activiteit"], "type": "string", "enum": [ "Telefonisch", "E-mail", "Inschrijving activiteit", "Persoonlijk", "Anders", "Doorverwijzing", "Adviesgesprek", "Kennismakingsgesprek", "Niet van toepassing" ] }, "performedAt": { "title": "Interaction date", "description": "Date and time when the interaction took place.", "examples": ["2024-01-01T10:00:00.000Z"], "type": "string", "format": "date-time", "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" }, "summary": { "title": "Interaction summary", "description": "Short summary of the interaction.", "examples": ["Initial intake call"], "type": "string" }, "notes": { "title": "Interaction notes", "description": "Additional notes or details about the interaction.", "examples": ["Candidate was enthusiastic"], "type": "string" }, "duration": { "title": "Interaction duration", "description": "Duration of the interaction in seconds.", "examples": [1800], "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "createdAt": { "title": "Interaction created at", "description": "Timestamp when the interaction record was created.", "examples": ["2024-01-01T10:00:00.000Z"], "type": "string", "format": "date-time", "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" } }, "required": ["id", "type", "performedAt", "createdAt"], "additionalProperties": false } }, "customFields": { "title": "Candidate custom fields", "description": "Dynamically generated custom candidate fields derived from the Airtable base schema.", "type": "object", "properties": { "Example Custom Field": { "title": "Example Custom Field", "examples": ["example 1", "example 2"], "type": "array", "items": { "type": "string", "enum": ["example 1", "example 2"] } } }, "additionalProperties": false } }, "required": ["name", "email", "privacyConsent"], "additionalProperties": false } ``` :: ## How to use the schema The schema is primarily intended as a **development and discovery tool**. It allows you to: - Explore the complete candidate data model. - Discover region-specific custom fields. - Validate request payloads during development and integration. - Generate forms or SDK validation logic if useful. While the schema can be used at runtime for dynamic payload construction or UI generation, this is optional. ## Custom field validation behavior Custom field validation is intentionally **lenient by design** to ensure that integrations remain stable even as region-specific schemas evolve. Key characteristics: - **Only known custom field properties are validated.** Fields in the current region schema are checked against their types and constraints. - **Unknown or outdated custom fields are ignored.** They do not cause the request to fail. - **Custom fields change infrequently.** Schema updates are designed not to break existing integrations. ### Disabling custom field validation You can disable custom field validation entirely by setting the `skipCustomFieldValidation` query parameter. When this flag is enabled: - No field-level validation is performed on `customFields` - The only check applied is whether `customFields` contains **valid JSON** This can be useful in scenarios where: - You are migrating data - You are forwarding payloads from another system - You want to decouple schema updates from deployment timelines For best results, retrieve and review the schema during development and update your integration when schema changes are introduced. # Requests Learn how to obtain an initial API key and how to authenticate your requests to the API. The API uses simple API key–based authentication. This key must be included in every request via the `x-api-key` HTTP header. ::callout{color="warning" icon="i-lucide-triangle-alert"} The legacy `api-key` header is deprecated. It remains available until the next major release. :: ## Requesting an API key An initial API key for your region must be requested by an employee of the relevant education region. We do not issue API keys directly to third-party vendors or external developers without involvement and approval from the region itself. Once approved, the region is responsible for managing the API key and coordinating its use with any third-party applications or integration partners. API keys do not expire. The region that receives the key is responsible for its secure storage and use, including any third-party integrations that rely on it. There is no way to retrieve a lost API key. Store it securely and consider creating a second key as a backup. ## Example requests ::code-group ```ts [TypeScript] const response = await fetch( "https://onderwijsregio.onderwijsin.nl/api/v2/schema", { method: "GET", headers: { "x-api-key": process.env.ONDERWIJSREGIO_API_KEY as string, "Content-Type": "application/json", }, } ); const data = await response.json(); ``` ```php [PHP] $ch = curl_init("https://onderwijsregio.onderwijsin.nl/api/v2/schema"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "x-api-key: " . getenv("ONDERWIJSREGIO_API_KEY"), "Content-Type: application/json", ], ]); $response = curl_exec($ch); curl_close($ch); ``` ```python [Python] import os import requests response = requests.get( "https://onderwijsregio.onderwijsin.nl/api/v2/schema", headers={ "x-api-key": os.environ["ONDERWIJSREGIO_API_KEY"], "Content-Type": "application/json", }, ) data = response.json() ``` ```bash [cURL] curl https://onderwijsregio.onderwijsin.nl/api/v2/schema \ -H "x-api-key: $ONDERWIJSREGIO_API_KEY" \ -H "Content-Type: application/json" ``` :: # Key management Learn how to create new API keys or rotate your keys. This guide explains how to manage API keys for your region. You can create new keys, rotate existing ones, and safely revoke keys that are no longer needed. You can use any of your existing API keys to do so. API keys are always scoped to a **region**. To manage keys, you must first know your region’s internal identifier. ## Determining your region ID Before managing API keys, retrieve your region details using the authenticated region endpoint: `GET /auth/regions/me` This endpoint returns information about the currently authenticated region, including its internal `id`. You will use this `id` when creating or deleting API keys. ## Creating a new API key To create a new API key for a region, use: `POST /auth/region/api-keys` ::callout{color="warning" icon="i-lucide-triangle-alert"} The full API key is returned only once. Afterwards, only a short, non-sensitive preview is available. Copy and store the key securely immediately. :: Provide a description to document the key’s purpose, such as “CRM integration” or “Sandbox testing”. ## Listing existing API keys API keys associated with a region are returned as part of the region object. You can inspect existing keys by calling: `GET /auth/regions/me` The response includes all API keys for the region, each with: - An internal key ID - A token preview - Metadata such as creation and update timestamps The full secret value is never returned for existing keys. ## Rotating API keys API keys do not expire automatically. To rotate a key safely: ::steps ### Create a new API key Keep the existing key active while you prepare the change. ### Update your integration Deploy the new key to every system that uses the old one. ### Verify the integration Confirm that requests authenticate successfully. ### Revoke the old key Delete the old key only after verification is complete. :: This approach avoids downtime and allows gradual rollout across multiple systems. ## Deleting an API key To revoke an API key, use: `DELETE /auth/region/api-keys/{id}` Where: `{id}` is the internal identifier of the API key. Deletion is prevented if the key is the **only remaining API key** for the region. Each region must always have at least one active API key. ## Best practices - Create **separate API keys per integration** or environment. - Use **clear descriptions** to document each key’s purpose. - **Store API keys securely** and never commit them to source control. - **Rotate keys periodically** or immediately if you suspect compromise. - Use the **sandbox environment** for testing whenever possible. # Candidate sessions Candidates can view and update personal information stored in the ATS. These requests use session-token authentication. Candidates can securely view and manage their personal data through session-based authentication. This mechanism is designed to protect sensitive information while giving candidates full transparency and control over how their data is used. In most cases, this functionality is fully handled by the platform and requires no additional work from regions or integration partners. However, the API also supports custom candidate-facing flows for regions that wish to build their own access portals. ## Candidate self-service experience Candidates have access to a dedicated user interface where they can: - View and update parts of their personal information. - Manage privacy consent. - See which regions can access their data. - Request deletion of their personal information. This interface is available at: {rel=""nofollow""} All authentication, authorization, and session handling for this experience is managed by the system itself. ## Session-based authentication Candidate requests are authenticated using **session tokens**. These tokens represent a verified candidate session and are separate from region or admin API keys. Session tokens are: - Issued only after successful identity verification. - Scoped to one candidate. - Used exclusively for candidate-facing endpoints. Regions do not need to manage or store these tokens unless they are building custom candidate-facing functionality. ## Building a custom candidate access flow (optional) Regions may choose to build their own “request access” or candidate portal experience. This is optional and intended for advanced integrations. The flow works as follows: ::steps ### Collect the candidate’s email address Ask for the candidate’s email address in your custom interface. ### Request email verification Call `POST /auth/candidates/request-email-verification` from your system. ### Let the candidate verify access When the email matches one or more candidates, the system sends a verification link. ### Continue in the official environment After verification, the candidate is redirected to the official candidate environment and a session is established. :: At this point, the candidate can continue managing their data using the standard tools provided by the platform. ## What regions need to know - You do **not** need to implement candidate session handling to remain compliant. - Candidate privacy, consent management, and deletion requests are enforced centrally. - Custom candidate access flows are supported, but optional. - Session tokens are never interchangeable with API keys and must not be used for region-level operations. # Mutation Engine The Mutation Engine is the system responsible for all write operations in our API. Any request that **creates, updates, or deletes data** is handled by the Mutation Engine. Instead of executing mutations immediately, requests are **queued, validated, serialized, and processed asynchronously**. This is not an implementation detail — it is a **core design choice** that guarantees consistency, reliability, and predictable behavior under load. ## Why the Mutation Engine exists Directly writing to Airtable from a public API introduces hard problems: - Strict rate limits. - Eventual consistency. - Transient failures. - Ordering issues. - Retry storms. - Client-side duplication and race conditions. Exposing those constraints directly to API consumers would make the API fragile and difficult to use. The Mutation Engine exists to **absorb that complexity** so clients don’t have to. **Our goals are simple:** - Mutations execute **in the correct order**. - Temporary failures are retried automatically. - Rate limits are respected centrally. - Clients receive **clear, consistent outcomes**. - No mutation is lost or executed twice unintentionally. ## Follow the mutation lifecycle ::steps ### Send a mutation request Call `POST /v2/...` to create, update, or delete data. You can include `idempotencyKey` and `callbackUrl`. ### Receive validation and acknowledgement The API validates the request immediately. Valid mutations are queued and receive an immediate acknowledgement. ### Wait for queued processing Each Airtable base has an internal queue. Mutations are processed one at a time, in order. ### Let the engine execute the mutation The engine applies rate limits centrally and retries temporary failures automatically. ### Receive the outcome Use a signed callback, when provided, or observe your own system state. :: ## Handle Mutation Engine responses Upon requesting a mutation, you can receive either an acknowledgement or an internal failure response. The following examples show the response types. ::code-group ```json [accepted.json] { "statusCode": 202, "message": "The Mutation Engine has accepted the request for processing", "data": { "mutationId": "71823522-1bfb-49b8-885f-ceea9d782ad2", "status": "queued" } } ``` ```json [validation-failure.json] { "error": true, "url": "https://onderwijsregio.onderwijsin.nl/api/v2/candidates", "statusCode": 500, "statusMessage": "Server Error", "message": "Mutation engine rejected the mutation request due to validation errors", "data": { "idempotencyKey": "your-key", "issues": [ ... List of Zod validation issues ... ] } } ``` ```json [engine-failure.json] { "error": true, "url": "https://onderwijsregio.onderwijsin.nl/api/v2/candidates", "statusCode": 500, "statusMessage": "Server Error", "message": "Unknown error from mutation engine", "data": { "idempotencyKey": "your-key" } } ``` ```json [declined.json] { "error": true, "url": "https://onderwijsregio.onderwijsin.nl/api/v2/candidates", "statusCode": 500, "statusMessage": "Server Error", "message": "Mutation engine declined the mutation request: {message}", "data": { "idempotencyKey": "your-key" } } ``` ```json [idempotency-hit.json] { "statusCode": 202, "message": "The Mutation Engine has accepted the request for processing", "data": { "mutationId": "71823522-1bfb-49b8-885f-ceea9d782ad2", "status": "idempotency_hit", "idempotencyKey": "your-idempotency-key", } } ``` :: ::note An internal Mutation Engine validation failure does not mean your payload is invalid. Payload validation happens before the request reaches the engine, so you should not normally encounter this response. :: ## Idempotent Operations You may provide an `idempotencyKey` with mutation requests. If the same key is sent multiple times: - The mutation executes only once. - Duplicate requests return the same `mutationId`. This is strongly recommended for: - Network retries. - Client restarts. - Exactly-once semantics. ## Callback URLs Because mutations are asynchronous, the recommended way to observe completion is via **callbacks**. If you provide a `callbackUrl`: - The API sends the result by `POST` when processing finishes. - Callbacks are signed and retry-safe. - Delivery failures are retried independently. Callback payloads include: - `mutationId` - `idempotencyKey` (if provided) - Final status (`completed` or `failed`) - Result or error details ::code-group ```json [success.json] { mutationId: '', idempotencyKey: '', status: 'completed', result: { /* operation-specific result */ } } ``` ```json [failure.json] { mutationId: '', idempotencyKey: '', status: 'failed', result: { errorCode: '', status: '', message: '', attempts: '', } } ``` :: It is important that your callback endpoint rejects requests from untrusted sources. You should verify the signature included with each callback to ensure it originated from the Mutation Engine. [Read the callback-signatures guide](https://docs.onderwijsregio.onderwijsin.nl/mutations/callback-signatures) to verify that callbacks were sent by the Mutation Engine. ## Designing Your Integration When integrating with the Mutation Engine, you should: - Treat mutation responses as **acknowledgements**, not results. - Use `idempotencyKey` for safety. - Use callbacks for completion and error monitoring. - Design your system to be **event-driven**, not synchronous. - Assume mutations can take seconds, not milliseconds. This model leads to **simpler, safer, and more resilient systems**. # Callback signatures Learn how to verify that requests to your callback endpoint are sent by the Mutation Engine. It is important that your callback endpoint rejects requests from untrusted sources. You should verify the signature included with each callback to ensure it originated from the Mutation Engine, and have not been tampered with in transit ## Overview Mutation callbacks are delivered asynchronously via HTTP `POST`. Because callback URLs are user-provided and therefore untrusted, **every callback request is cryptographically signed**. Signature verification allows you to confirm that: - The callback originated from the Mutation Engine - The request body and URL were not altered in transit - The request is not a replay of a previous callback Whether or not you verify signatures in productions integrations is up to you, but we strongly recommend doing so. ## Inspect the callback request Each callback request has the following properties: - **Method:** `POST` - **Body:** JSON mutation-result payload. - **Headers:** Signature-related metadata, described below. Callbacks use a max timeout of 15 seconds. If your server does not respond within that window, the Mutation Engine will throw an error and retry. ## Signed Headers Every callback request includes these HTTP headers: - `x-mutationengine-timestamp` - `x-mutationengine-nonce` - `x-mutationengine-signature` - `x-mutationengine-sandbox-enabled` There is no key identifier header. All callbacks currently use a single region-specific signing secret. ## What Is Signed The signature covers the following inputs: - The callback URL **path and query string**. - The raw JSON request body. - The timestamp. - The nonce. Any change to **any** of these values will invalidate the signature. ## Signature Format The signature header has the form: ```http x-mutationengine-signature: v2= ``` Where: - `v2` is the signature version. - The value after `v2=` is a **Base64-encoded HMAC-SHA256** digest. - The HMAC secret is your **region-specific callback signing secret**. ## Retrieving Your Signing Secret Your callback signing secret can be retrieved using the `/auth/regions/me` endpoint. Signing secrets are **region-scoped**, not API-key–scoped. If you receive callbacks from multiple regions, you must verify them using the corresponding regional secret. ## Canonical String To verify a callback, you must reconstruct the **canonical string-to-sign** exactly as it was produced by the Mutation Engine. ```text ``` **Important details:** - Fields are separated by newline characters (`\n`). - A **trailing newline is required**. - The canonical string has **no version line**. - `` is the lowercase hexadecimal SHA-256 hash of the **raw JSON body**. ### Field Definitions - ``:br Value of `x-mutationengine-timestamp` (Unix time in milliseconds) - ``:br Value of `x-mutationengine-nonce` (UUID v4) - ``:br Callback URL path plus query string (e.g. `/webhooks/engine-callback?foo=bar`) - ``:br SHA-256 hash of the raw request body, hex-encoded ### Example Canonical String ```text 1766494092286 550e8400-e29b-41d4-a716-446655440000 /webhooks/mutation a9c2e4b7f4c8... ``` ## Verify a callback To verify a callback request: ::steps ### Read the raw body Read the **raw request body** before parsing JSON. ### Hash and reconstruct Compute the body’s hex-encoded SHA-256 hash and reconstruct the canonical string **exactly**. ### Calculate the expected signature Use the regional signing secret to create an HMAC-SHA256, then Base64-encode it. ### Compare securely Remove `v2=` from the received signature, compare values in constant time, and reject failures. :: ## Replay Protection The timestamp and nonce are included to mitigate replay attacks. We recommend that you: - Reject callbacks with timestamps older than 15 minutes - Track recently seen nonces and reject duplicates (optional but recommended) ## Failure Behavior If your callback endpoint responds with a non-2xx status code: - The callback will be retried automatically - Retries use exponential backoff - Callback delivery failures **do not affect mutation execution** Callbacks are a delivery mechanism only. A mutation may succeed even if all callback attempts ultimately fail. ## Examples The examples below mirror the Mutation Engine’s signing logic, including: - Canonical string layout - SHA-256 body hashing - Base64-encoded HMAC - Required trailing newline ::code-group ```ts [Node.js] import crypto from 'crypto' export function verifyCallbackSignature( req: { headers: Record rawBody: string url: string }, signingSecret: string ): boolean { const timestamp = req.headers['x-mutationengine-timestamp'] const nonce = req.headers['x-mutationengine-nonce'] const signatureHeader = req.headers['x-mutationengine-signature'] if (!timestamp || !nonce || !signatureHeader) return false if (!signatureHeader.startsWith('v2=')) return false const receivedSignature = signatureHeader.slice(3) const url = new URL(req.url) const pathAndQuery = `${url.pathname}${url.search}` const bodyHashHex = crypto .createHash('sha256') .update(req.rawBody, 'utf8') .digest('hex') const canonical = [ timestamp, nonce, pathAndQuery, bodyHashHex, '' ].join('\n') const expectedSignature = crypto .createHmac('sha256', signingSecret) .update(canonical, 'utf8') .digest('base64') return crypto.timingSafeEqual( Buffer.from(expectedSignature), Buffer.from(receivedSignature) ) } ``` ```php [PHP] bool: """ Verify a Mutation Engine callback signature. :param headers: Request headers (lowercased keys recommended) :param raw_body: Raw request body (exact bytes received) :param request_url: Full request URL :param signing_secret: Region-specific callback signing secret :return: True if the signature is valid """ timestamp = headers.get("x-mutationengine-timestamp") nonce = headers.get("x-mutationengine-nonce") signature = headers.get("x-mutationengine-signature") if not timestamp or not nonce or not signature: return False if not signature.startswith("v2="): return False received_signature = signature[3:] parsed = urlparse(request_url) path_and_query = parsed.path if parsed.query: path_and_query += "?" + parsed.query body_hash_hex = hashlib.sha256(raw_body).hexdigest() canonical = ( f"{timestamp}\n" f"{nonce}\n" f"{path_and_query}\n" f"{body_hash_hex}\n" ) expected_signature = base64.b64encode( hmac.new( signing_secret.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256, ).digest() ).decode("ascii") return hmac.compare_digest(expected_signature, received_signature) ``` :: Use a constant-time comparison in every implementation: `timingSafeEqual` in Node.js, `hash_equals` in PHP, and `hmac.compare_digest` in Python. Always read raw request bytes and combine verification with timestamp freshness checks and optional nonce tracking. # Submitting candidates Learn how to create new candidates in the applicant tracking system (ATS) of your education region, including interactions and attachments, using the asynchronous Mutation Engine. This article focuses on **initial submission** of candidate data. Finding existing candidates and updating them are covered in subsequent articles. ## Start with the `/schema` endpoint Before submitting any candidate data, you should **always retrieve the schema for your region** using the `/schema` endpoint. The candidate data model is: - **Region-specific.** - **Partially dynamic.** - **Strictly validated at runtime.** The `/schema` endpoint returns the **authoritative JSON Schema** that the API uses internally to validate candidate payloads. There is no abstraction or translation layer between this schema and the actual validation logic. You should always base payload construction and client-side validation on the response of this endpoint. If a payload validates against the schema returned by `/schema`, **it will validate against the API**. [Read the schema guide](https://docs.onderwijsregio.onderwijsin.nl/getting-started/schema) before creating a payload. ## Candidate creation endpoints The API provides multiple ways to submit candidate data, depending on your use case. ### Create one or more candidates Use `POST /candidates` to create **one or more new candidates** in a single request. You can create **up to 10 candidates per request**. **Key characteristics:** - Accepts an **array of candidate payloads**. - Supports inline interactions, attachments, and custom fields. - Is processed asynchronously by the Mutation Engine. - Returns `202 Accepted`. This endpoint is ideal for: - Bulk imports - Form submissions batched together - Initial data migrations ### Upsert a candidate Use `POST /candidates/upsert` when you want to **create or update a candidate in a single call**. **Behavior:** - If a candidate with the same email or phone exists, it is **updated**. - Otherwise, the API **creates a new candidate**. **Custom fields are only applied when a candidate is created, not when an existing candidate is updated via upsert.** This endpoint is useful when: - You do not know whether a candidate already exists, for example in forms in your website or application. - You want idempotent, fire-and-forget behavior ## Inlining related items When creating candidates, you may include **interactions and attachments directly in the create payload**. ### Inline interactions You can include an `interactions` array when creating a candidate. - Up to **10 interactions** per candidate - Each interaction is validated against the interaction schema - Interactions are created atomically with the candidate This is useful for: - Logging an initial intake call - Capturing a first contact moment - Registering signups to events - Importing historical interaction data Inline interaction creation is **only supported when creating candidates**, not when updating them. You can add interactions to existing candidates with the `POST /candidates/:id/interactions` endpoint. ### Inline attachments Attachments can be included using: - `curriculumVitae` - `otherAttachments` Each attachment can be provided as a **public URL** or **Base64-encoded data** (including data URI prefix). **Limits:** - Maximum of **5 attachments per field** - Attachments are uploaded and linked as part of the mutation Inlining attachments is convenient when: - Submitting form uploads - Migrating candidate documents - Creating a fully populated candidate in one request ## Mutation Engine response All candidate creation endpoints are handled by the **Mutation Engine**. As a result, write operations are **asynchronous**. ### What a `202` response means A successful submission returns a `202 Accepted` response, indicating that: - The request was **validated successfully** - The mutation was **accepted and queued** - Processing will happen asynchronously ```json [response.json] { "statusCode": 202, "message": "The mutation request has been accepted for processing.", "data": { "status": "accepted", "idempotencyKey": "a_unique_key_generated_for_this_request_12345", "message": "The mutation request has been accepted for processing." }, "meta": { "sandboxEnabled": false } } ``` ### Important implications - A `202` response does **not** confirm that the candidate exists yet. - Treat the response as an **acknowledgement**, not a result. - Use `idempotencyKey` to retry safely. - Use `callbackUrl` for completion and failure notifications. Design your integration to be **event-driven**, not synchronous [Read the Mutation Engine guide](https://docs.onderwijsregio.onderwijsin.nl/mutations/mutation-engine) to understand asynchronous processing. ## Supported field values Some fields only accept predefined values. The most important standardized options are listed below. ::code-group ```text [Phases] nieuw matchen oriënteren voorbereiden in opleiding werkzaam uitgestroomd ``` ```text [Sectors] Primair onderwijs Voortgezet onderwijs Speciaal onderwijs Middelbaar beroepsonderwijs Hoger onderwijs Praktijkonderwijs ``` ```text [Roles] Docent / leraar Instructeur (mbo) OOP Ondersteuning Leerlingenzorg Midden management Schoolleiding Anders ``` ```text [Qualifications] Primair onderwijs Beperkt tweedegraads Tweedegraads Eerstegraads PDG BKO/BDB Kwalificatie onderwijs ondersteunend personeel Kwalificatie Instructeur (MBO) Geen: gastdocentschap Pabo Onbekend Nog geen idee Niet van toepassing ``` ```text [Prior education] geen praktijkonderwijs vmbo-bl vmbo-kl vmbo-gl vmbo-tl vmbo havo vwo mbo entree mbo 2 mbo 3 mbo 4 mbo associate degree hbo propedeuse hbo bachelor hbo master hbo wo bachelor wo master wo PhD ``` ```text [Subject preferences] Nederlands Engels Wiskunde Wiskunde A Wiskunde B Wiskunde C Wiskunde D Frans Duits Spaans Latijnse taal en cultuur Griekse taal en cultuur Klassieke talen KCV Geschiedenis Aardrijkskunde Maatschappijleer Maatschappijkunde Maatschappijwetenschappen Filosofie Levensbeschouwing Economie Algemene economie Bedrijfseconomie Ondernemerschap - Academische vaardigheden Natuurkunde Scheikunde Biologie Rekenen NLT (Natuur, leven en techniek) Algemene Natuurwetenschappen NaSk Science Life sciences Informatica Techniek Technologie en toepassing Toepassingsgericht onderwijs Programmeren installeren en energie Produceren installeren energie (PIE) Art en Technologie Digital Creative Technology (DCT) Digital Technology Digitaal burgerschap STEAM T&T CKV Kunst & Techniek Kunst: algemeen Kunst: beeldend Kunst: beeldend / tekenen Kunst: dans Kunst: drama Kunst: podium Kunst: tekenen Kunst: theater Kunstgeschiedenis/CKV Handvaardigheid Muziek Vormgeving en media Lichamelijke opvoeding Lichamelijke opvoeding 2 Bewegen sport en maatschappij (BSM) Dienstverlening en producten Praktijkgericht programma (havo) Praktijkprogramma zorg (mavo) Zorg en welzijn Mens en dienstverlening Mens en Maatschappij Mens en Natuur Onderzoeken & Ontwerp Onderzoekend leren Onderzoeksvaardigheden Millennium Skills Global perspectives Global citizenship Big History International Baccalaureate Psychologie Persoonlijke Vorming PGP maatschappij PGP P&O PGP technologie LOB/PWS PWS Zomer PWS Leefstijl Kennis van het geestelijk leven Vrede en Recht Wetenschap Wetenschapsoriëntatie Humaniora Arabisch Chinees Italiaans Russisch NT2 Econasium E&O ``` ```text [Interaction types] Telefonisch E-mail Persoonlijk Anders Doorverwijzing Adviesgesprek Kennismakingsgesprek Inschrijving activiteit Niet van toepassing ``` :: Always rely on the `/schema` endpoint for the **complete and current list** of allowed values. ## Consider schema-validation performance To validate the payload when submitting a candidate, we must internally perform schema introspection for your region. The same performance limitations apply as when you call the `/schema` endpoint; Airtable’s database introspection is inherently slow. [Read the schema guide](https://docs.onderwijsregio.onderwijsin.nl/getting-started/schema) for the complete field model. If our system does not hit a valid cache entry, this introspection will take 3 - 6 seconds, which will directly affect the response time for your request. The Mutation Engine cannot avoid this delay because validation happens *before* a mutation is queued. Field schemas are cached for 72 hours. ### Skipping Custom Field validation If your payload does not include `customFields`, set `skipCustomFieldValidation` in the request query parameters to skip schema introspection and validation. This can significantly improve performance without a cached schema. This flag is supported for `POST /candidates` and `POST /candidates/upsert`. ## Test in the sandbox All candidate creation endpoints support sandbox mode via: - The `x-api-sandbox: true` header, or - The `/sandbox` endpoint prefix This allows you to test the candidate creation flows without affecting production data. # Finding candidates Choose the right API endpoint to list, search, or retrieve candidate records. This section describes the different ways you can **retrieve and search candidate data** using the Onderwijsregio API. Depending on your use case—bulk access, targeted lookup, or candidate self-access—different endpoints are available. ## Choose a discovery method The API exposes three primary ways to find candidates: | | | | | -------------------- | ---------------------- | ----------------------------------------- | | **Method** | **Endpoint** | **Typical use case** | | *List candidates* | `GET /candidates` | Browse or sync all candidates in a region | | *Find by identifier* | `GET /candidates/find` | Look up candidates by email or phone | | *Fetch by ID* | `GET /candidates/{id}` | Retrieve a specific candidate record | All endpoints described below operate **within the authenticated region** and support sandbox mode via the `x-api-sandbox` header. ## List candidates in bulk Use `GET /candidates` to retrieve all candidates belonging to your region. This endpoint is designed for integrations that need to **synchronize or browse candidate data**. Key characteristics: - Cursor-based pagination. - Optional field selection. - Region-scoped access through API-key authentication. - [Rate limits apply](https://docs.onderwijsregio.onderwijsin.nl/misc/troubleshooting). - A maximum `pageSize` of 100 records. ```http GET /candidates?pageSize=10&cursor=abc123 ``` The response includes: - `data`: an array of candidate records - `meta.cursor`: a cursor for fetching the next page (or `null` if finished) ::note Follow the cursor until it becomes `null` to retrieve the complete dataset. :: ## Find candidates by email or phone For targeted lookups, use `GET /candidates/find`. This endpoint allows you to search for candidates using **email address or phone number**. Rules: - At least one of `email` or `phone` must be provided - If both are supplied, **email takes precedence** - Email matching is case-insensitive - Phone numbers ignore non-numeric characters ```http GET /candidates/find?email=jane.doe@example.com ``` This method is ideal for: - Finding records based on user input - Resolving inbound leads - Matching external systems to existing records ## Retrieve a candidate by ID Once you know a candidate’s unique identifier, you can retrieve the full record using `GET /candidates/{id}`. Features: - Supports both API key and candidate session authentication - Optional field filtering - Optional inclusion of interaction records ```http GET /candidates/recAbC13pTbjpS6Iz?includeInteractions=true ``` By default all fields are returned that are part of the candidates table. (Related) interactions must be explicitly requested. ## Limit returned fields Most candidate endpoints support the `fields` query parameter. This allows you to limit responses to only the data you need, improving performance and reducing payload size. ```http GET /candidates?fields=name&fields=email&fields=phase ``` If omitted, all standard shared fields are returned (excluding interactions and custom fields). ## Enable candidate self-access Candidates themselves can retrieve their own records using `GET /auth/candidates/me`. This endpoint relies on a **session bearer token**, not an API key. Use cases include: - Candidate portals - Self-service dashboards - Verification flows The response may include **multiple candidate records** if the session (e.g. email) is linked to more than one candidate. A candidate can also access their own data via `/candidates/{id}` with session authentication. ## Test discovery flows in the sandbox All candidate discovery endpoints support sandbox mode via: - The `x-api-sandbox: true` header, or - The `/sandbox` endpoint prefix This allows you to test candidate search flows without affecting production data. # Updating candidates Learn how to update candidate data, manage related records, or remove candidates from the system. All write operations are handled asynchronously by the Mutation Engine and follow the same validation and queuing guarantees as candidate creation. This article focuses on **updating and deleting existing candidates**. Creating candidates and finding them are covered in separate articles. ## Start with the `/schema` endpoint (again) Before updating a candidate, you should retrieve the schema using the `/schema` endpoint—this time with `operation=update`. ```http GET /schema?operation=update ``` Update operations differ from create operations: - **All fields are optional** - Patch semantics apply (only provided fields are changed) - Validation rules still apply to any fields you include The update schema reflects these differences exactly. Required fields from creation are relaxed, but **type constraints, enums, and structure are still enforced**. If a payload validates against the update schema, **it will validate against the update endpoint**. [Read the schema guide](https://docs.onderwijsregio.onderwijsin.nl/getting-started/schema) for the authoritative candidate payload definition. ## Updating a candidate by ID Use `PATCH /candidates/{id}` to update an existing candidate. ```http PATCH /candidates/{id} ``` ### Understand update behavior - At least **one field must be provided** - Only fields present in the payload are updated - Fields omitted from the request remain unchanged - Validation happens before the mutation is queued - Processing is asynchronous via the Mutation Engine This endpoint is intended for: - Updating candidate details - Changing phase or preferences - Correcting or enriching existing data ## Update semantics and constraints ### Patch behavior The update endpoint uses patch semantics: - Arrays are **replaced**, not merged - Scalar values overwrite existing values - Sending `null` explicitly clears a nullable field You should always construct update payloads deliberately and only include fields you intend to change. ### Custom fields **Patching custom field values is not supported**. You cannot change the values of custom fields via the API after a record has been created. We plan to address this limitation in the next major release. ## Archiving vs deleting There are two ways to remove a candidate, with very different consequences. ### Archive a candidate ```http POST /candidates/{id}/archive ``` **Archiving** anonymizes the candidate record, makes it permanently unretrievable, and preserves the integrity of historical reporting. If a `callbackUrl` is provided, the **candidate record before archival** is sent to your callback endpoint. In most cases, you should **archive** a candidate instead of deleting them. ### Delete a candidate ```http DELETE /candidates/{id} ``` **Deleting** permanently removes the candidate record, excludes it from historical reporting, and cannot be undone. This endpoint should only be used in exceptional cases, such as test data or invalid data cleanup. ## Test update flows in the sandbox All candidate update endpoints support sandbox mode via: - The `x-api-sandbox: true` header, or - The `/sandbox` endpoint prefix This allows you to test the candidate update flows without affecting production data. # Interactions Learn how to create, update, list, and delete interactions associated with candidates. Interactions represent contact moments or events—such as phone calls, emails, or meetings—and are modeled as first-class records linked to a candidate. This article focuses exclusively on **interaction management**. Creating and updating candidates is covered in separate articles. ## What are interactions? Interactions capture **when and how contact occurred** with a candidate. Typical examples include: - Intake calls - Emails - Advice or orientation meetings - Event registrations Interactions are always **scoped to a single candidate** and are stored as separate records linked to that candidate. ## Interaction lifecycle overview Interactions can be: - **Listed** for an existing candidate - **Created** (inline during candidate creation, or separately afterward) - **Updated** - **Deleted** All interaction write operations are handled asynchronously by the **Mutation Engine**. [Read the Mutation Engine guide](https://docs.onderwijsregio.onderwijsin.nl/mutations/mutation-engine) for the asynchronous write model. ## Listing interactions Use `GET /candidates/{id}/interactions` to retrieve all interactions linked to a candidate. ```http GET /candidates/{id}/interactions ``` **Characteristics**: - Returns all interaction records for the candidate (up to 100 records) - Results are scoped to the authenticated region - Supports sandbox mode This endpoint is typically used for: - Candidate timelines - CRM-style overviews - Auditing or reporting ## Creating interactions ### Creating interactions for an existing candidate Use `POST /candidates/{id}/interactions` to create one or more interactions for an existing candidate. ```http POST /candidates/{id}/interactions ``` **Characteristics:** - Up to **10 interactions per request** - Each interaction must include: - `type` - `performedAt` - Validation happens before the mutation is queued - Ownership is enforced during mutation execution This endpoint is ideal when: - Logging follow-up actions - Adding interactions after candidate creation - Syncing interactions from external systems ### Inline interaction creation (during candidate creation) Interactions can also be created **inline** when creating a candidate via `POST /candidates`. This allows you to: - Create a candidate and initial interactions atomically - Avoid follow-up API calls for first contact moments Inline interaction creation is **only supported during candidate creation**. It is not supported when updating a candidate. [Read the candidate-submission guide](https://docs.onderwijsregio.onderwijsin.nl/candidates/submitting-candidates) for inline interactions. ## Interaction payload basics Each interaction payload follows the same schema, whether created inline or via the interaction endpoint. Common fields include: - `type`:br One or more predefined interaction types. - `performedAt`:br Date or date-time when the interaction took place. - `summary` *(optional)*:br Short description of the interaction. - `notes` *(optional)*:br Additional details. - `duration` *(optional)*:br Duration in seconds. Allowed values and exact validation rules are defined by the `/schema` endpoint and enforced at runtime. [Read the schema guide](https://docs.onderwijsregio.onderwijsin.nl/getting-started/schema) for the authoritative interaction definition. ## Updating interactions Use `PATCH /candidates/{id}/interactions/{interactionId}` to update an existing interaction. ```http PATCH /candidates/{id}/interactions/{interactionId} ``` **Characteristics:** - At least **one field must be provided** - Only included fields are updated - Patch semantics apply - Validation happens before the mutation is queued This endpoint is typically used to: - Correct dates or summaries - Add notes after the fact - Adjust interaction metadata ## Deleting interactions Use `DELETE /candidates/{id}/interactions/{interactionId}` to remove an interaction. ```http DELETE /candidates/{id}/interactions/{interactionId} ``` Deleting an interaction **permanently removes the interaction** record from the candidate’s history. ## Mutation Engine responses All interaction mutations return a `202 Accepted` response. ### What a `202` response means A successful submission returns a `202 Accepted` response, indicating that: - The request was **validated successfully** - The mutation was **accepted and queued** - Processing will happen asynchronously ```json [response.json] { "statusCode": 202, "message": "The mutation request has been accepted for processing.", "data": { "status": "accepted", "idempotencyKey": "a_unique_key_generated_for_this_request_12345", "message": "The mutation request has been accepted for processing." }, "meta": { "sandboxEnabled": false } } ``` ### Important implications - A `202` response is **not** confirmation that the candidate exists yet - Treat the response as an **acknowledgement**, not a result - Use `idempotencyKey` to safely retry requests - Use `callbackUrl` to receive completion or failure notifications Design your integration to be **event-driven**, not synchronous ## Test interaction flows in the sandbox All interaction endpoints support sandbox mode via: - The `x-api-sandbox: true` header, or - The `/sandbox` endpoint prefix This allows you to test interaction flows without affecting production data. # Attachments Learn how to add and remove documents, images, and other files from candidate data. Attachments are managed through **dedicated endpoints** and are intentionally separated from general candidate updates to prevent accidental data loss and to make file operations explicit. This article focuses exclusively on **attachment management**. Creating and updating candidates is covered in separate articles. ## What are attachments? Attachments represent **files linked to a candidate**, such as: - Curriculum vitae (CV) - Certificates or diplomas - Portfolios - Supporting documents or images Attachments are stored externally and linked to a candidate record. They are treated as **append-only resources** unless explicitly removed. ## Attachment fields Attachments are grouped into two distinct fields: - `curriculumVitae`:br Files that represent the candidate’s CV. - `otherAttachments`:br Any other supporting documents. Each field has its own limits and must be managed explicitly. ## Attachment lifecycle overview Attachments can be: - **Added** to an existing candidate - **Removed** from a candidate Attachments are **not updated in place**. To change a file, you must remove the existing attachment and upload a new one. All attachment mutations are handled asynchronously by the **Mutation Engine**. [Read the Mutation Engine guide](https://docs.onderwijsregio.onderwijsin.nl/mutations/mutation-engine) to understand asynchronous processing. ## Adding attachments to a candidate Use `POST /candidates/{id}/attachments` to upload and link attachments to an existing candidate. ```http POST /candidates/{id}/attachments ``` **Characteristics:** - The candidate must already exist. - Existing attachments are **preserved**. - New attachments are **appended**. - An attachment payload can contain no more than five files per attachment field. - Provide at least one attachment field: `curriculumVitae` or `otherAttachments`. This endpoint is ideal for: - Uploading documents after initial candidate creation - Adding missing files - Incrementally enriching candidate records ## Attachment payload formats Each attachment can be provided in one of two ways. ### Attachment via URL Use this when the file is already publicly accessible. - The API fetches and stores the file - The URL must be reachable at request time - Supports **file size of up to 25MB** ### Attachment via base64 data Use this when uploading files directly. - Include base64-encoded data - The value must include the full data URI prefix - Support **file size of up to 5MB** Both formats are validated against the same schema and limits. ## Limits and constraints - Maximum of **5 attachments per field** - Filenames must be provided - Attachments are immutable once stored - Payloads are validated before the mutation is queued - Not all mime types are accepted | Category | MIME types | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | Images | image/png, image/jpeg, image/webp, image/gif, image/heic, image/heif | | PDFs | application/pdf | | Word / Office | application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.oasis.opendocument.text | | Spreadsheets | application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | | Presentations | application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation | | Text | text/plain, text/markdown, text/csv, application/rtf | | Structured text | application/json, application/xml | ## Removing attachments Use `DELETE /candidates/{id}/attachments` to remove one or more attachments from a candidate. ```http DELETE /candidates/{id}/attachments ``` **Characteristics:** - Attachments are removed by **attachment ID** - At least one attachment must be specified - Removal is permanent and cannot be undone ## Inline attachments during candidate creation Attachments can also be provided **inline** when creating a candidate via `POST /candidates`. This allows you to: - Create a fully populated candidate in a single request - Avoid follow-up attachment uploads for initial submissions Inline attachments are only supported during **candidate creation**, not during candidate updates. ## Mutation Engine responses All attachment operations return a `202 Accepted` response. ### What a `202` response means A successful submission returns a `202 Accepted` response, indicating that: - The request was **validated successfully** - The mutation was **accepted and queued** - Processing will happen asynchronously ```json [response.json] { "statusCode": 202, "message": "The mutation request has been accepted for processing.", "data": { "status": "accepted", "idempotencyKey": "a_unique_key_generated_for_this_request_12345", "message": "The mutation request has been accepted for processing." }, "meta": { "sandboxEnabled": false } } ``` ### Important implications - A `202` response is **not** confirmation that the candidate exists yet - Treat the response as an **acknowledgement**, not a result - Use `idempotencyKey` to safely retry requests - Use `callbackUrl` to receive completion or failure notifications Design your integration to be **event-driven**, not synchronous ## Sandbox support Attachment endpoints support sandbox mode via: - The `x-api-sandbox: true` header, or - The `/sandbox` endpoint prefix This allows you to safely test upload and removal flows without affecting production data. ## Attachment uploads may fail silently Attachment uploads are processed asynchronously by Airtable. As a result, a mutation may succeed even if one or more attachments fail to upload. In these cases, Airtable may either remove the attachment from the record or store it with a URL that later returns an error. Only after repeated failures may Airtable return an explicit `ATTACHMENTS_FAILED_UPLOADING` error. ::callout{color="warning" icon="i-lucide-triangle-alert"} A successful mutation response does not guarantee that every attachment was stored correctly. This Airtable limitation cannot be detected by the Mutation Engine. :: # MCP Server ## What is MCP? The Model Context Protocol (MCP) is a standard way for an AI application to discover and use external tools. Our public MCP server lets an AI agent look up the documentation and generated API reference while it helps you build an integration. The server is read-only. It does not expose your API token, modify Onderwijsregio API data, or make API requests on your behalf. ## What can an agent do? The server is available at [`https://docs.onderwijsregio.onderwijsin.nl/mcp`](https://docs.onderwijsregio.onderwijsin.nl/mcp){rel=""nofollow""} over HTTP transport. Use the discovery tools first, then retrieve the specific reference entry you need: - **`list-api-tags`** lists the functional groups in the API reference. - **`get-api-tag`** retrieves one tag and its associated operation summaries. - **`list-api-operations`** lists documented HTTP operations. - **`get-api-operation`** retrieves one operation by method and path, including request and response details. - **`list-api-models`** lists the named API schemas. - **`get-api-model`** retrieves one schema and its fields. Each reference result includes a link to the corresponding page on this documentation site. This makes the MCP server useful for questions such as: - Which endpoint should I use to retrieve articles or programmes? - What fields and relationships does a model contain? - Which operations are grouped under authentication or search? - Where can I find the complete reference page for an endpoint? ## Connect an AI tool Most MCP-compatible tools ask for the server URL. Use `https://docs.onderwijsregio.onderwijsin.nl/mcp` and choose HTTP or streamable HTTP when the client offers a transport choice. ### Claude Code Register the remote server from the command line: ```bash claude mcp add --transport http onderwijsregio-api https://docs.onderwijsregio.onderwijsin.nl/mcp ``` ### Cursor Add the server to `.cursor/mcp.json` in your project: ```json [.cursor/mcp.json] { "mcpServers": { "onderwijsregio-api": { "type": "http", "url": "https://docs.onderwijsregio.onderwijsin.nl/mcp" } } } ``` ### Visual Studio Code and other clients Add an HTTP MCP server to the client configuration using this endpoint: ```json { "name": "onderwijsregio-api", "type": "http", "url": "https://docs.onderwijsregio.onderwijsin.nl/mcp" } ``` Configuration names and file locations differ between clients. Follow the client’s MCP setup instructions if it uses a different configuration shape. ## Use the server effectively Ask the agent to consult the MCP server before it invents an endpoint, field name, or response shape. For a broad question, ask it to list tags, operations, or models first. For a known endpoint or schema, ask it to retrieve the exact reference entry directly. The MCP server provides documentation context; it does not replace authentication. Your application still needs an API account, token, and the permissions required for the data you request. ::note{title="Keep credentials separate"} Never paste an Onderwijsregio API token into an MCP configuration. The public server only needs its own URL, and the MCP tools are read-only. :: ## Related resources - [LLMs.txt](https://docs.onderwijsregio.onderwijsin.nl/agents/llms-txt) provides documentation files that AI tools can ingest as context. - [Request API access](https://docs.onderwijsregio.onderwijsin.nl/authentication/requests) explains how to obtain an API key and authenticate requests. - [API reference](https://docs.onderwijsregio.onderwijsin.nl/api-reference) contains the generated endpoint and schema pages. # LLMs.txt ## What is LLMs.txt? `llms.txt` is a convention for publishing documentation in a format that is easy for large language models to read. Instead of asking an AI tool to discover every page through a website, you can point it at a curated text file containing the important concepts, guides, and links. This is useful when an AI coding assistant needs reliable context about the Onderwijsregio API, data model, or permissions. ## Available files The documentation site publishes two files: - [`/llms.txt`](https://docs.onderwijsregio.onderwijsin.nl/llms.txt){rel=""nofollow""} is the compact starting point. It contains the key guidance and links and is suitable for most tools. - [`/llms-full.txt`](https://docs.onderwijsregio.onderwijsin.nl/llms-full.txt){rel=""nofollow""} contains the complete generated documentation, including detailed guides and API reference pages. It is substantially larger. Start with `/llms.txt`. Use `/llms-full.txt` when the agent needs details from several parts of the documentation and its context window can handle the larger file. ## Add the documentation to an AI tool The exact workflow depends on the tool. In general, add the URL as documentation, web context, or a project reference, then ask the agent to use it when answering API questions. ### Cursor and Windsurf Use the tool’s documentation or web-context feature to add one of these URLs: ```text https://docs.onderwijsregio.onderwijsin.nl/llms.txt https://docs.onderwijsregio.onderwijsin.nl/llms-full.txt ``` You can also mention the URL directly in a prompt when you want the context for a single task. ### Other AI tools For an assistant that accepts URLs as context, include a short instruction such as: :prompt{description="Use https://docs.onderwijsregio.onderwijsin.nl/llms.txt as the source of truth for Onderwijsregio API endpoints, models, and integration guidance."} If the tool supports MCP, consider connecting the [Onderwijsregio API MCP server](https://docs.onderwijsregio.onderwijsin.nl/agents/mcp-server) instead. MCP lets the agent discover only the reference entries it needs. ## Keep the source of truth clear The files are generated from the published documentation. They are a context aid, not an API credential and not a replacement for the live API response. Tell your agent to verify important details against the linked reference page and to account for your token’s permissions. Some datasets and services require additional access. When you ask an agent to write code, provide the intended use as well. For example, say whether you are retrieving articles, searching FAQs, building a programme finder, or working with media. ::tip{title="Use focused context first"} The compact file usually gives an agent enough orientation. Add individual documentation pages or use MCP when you need a precise endpoint, schema, or example. :: ## Related resources - [MCP Server](https://docs.onderwijsregio.onderwijsin.nl/agents/mcp-server) provides read-only, tool-based documentation discovery. - [Getting started](https://docs.onderwijsregio.onderwijsin.nl/getting-started) explains the available services and the first integration steps. # Troubleshooting This section describes known issues and recommended workarounds when integrating with the API. ## Why not provide direct access to the Airtable API? We intentionally do not provide direct access to the underlying Airtable API. The primary reason is data separation. The Airtable base is shared across multiple education regions, and direct access would make it possible to view or modify data belonging to other regions, which is not acceptable from a privacy and governance perspective. Additionally, the Airtable setup relies on strict internal conventions and constraints. While Airtable’s flexibility is a strength, it also makes it easy to unintentionally break data structures or workflows. The API layer acts as a controlled abstraction, enforcing validation and business logic to protect data integrity and system stability across all regions. ## Rate limits The Airtable API enforces strict rate limits—**no more than 5 requests per second**, shared across all regions. ### Understand the impact - **Mutations (create, update, and delete):** The Mutation Engine handles write-operation rate limiting. - **GET requests:** Rate limits apply to every read endpoint. A high volume of requests in quick succession can cause rate-limit errors. ::callout{color="warning" icon="i-lucide-triangle-alert"} For rate-limit errors on read requests, use retries with backoff, such as exponential backoff. The API may report these errors as `500` , not only as `429` . :: ### Use a resilient read strategy - Batch or cache read requests where possible. - Avoid tight request loops. - Add retry logic with increasing delays when rate-limit errors occur. ## Performance considerations for schema introspection The first `/schema` request for a region can be slow. Airtable performs database introspection to determine the region-specific schema. Later requests use a **cached response** and are typically much faster. Field schemas are cached for 72 hours. The same applies when you submit a candidate with `customFields`. The API must inspect your region’s schema before validating that payload. [Read the schema guide](https://docs.onderwijsregio.onderwijsin.nl/getting-started/schema) for the shared candidate data model and region-specific custom fields. If our system does not hit a valid cache entry, this introspection will take **approximately 3 - 5 seconds**, which will directly affect the response time for your request. The Mutation Engine cannot avoid this delay for `POST` requests because validation happens *before* a mutation is queued. [Read the Mutation Engine guide](https://docs.onderwijsregio.onderwijsin.nl/mutations/mutation-engine) to understand how write operations are processed. ## 403 vs. 404 errors For some lookups and record-ID operations, Airtable returns `403` when a record cannot be found. This can happen in these routes: - `[GET/PATCH/DELETE] /api/v2/candidates/:id/interactions/:interactionId` when `interactionId` does not exist in your region’s database. - `[POST/PATCH/DELETE] /api/v2/candidates/:id` when the candidate record cannot be found. ## Attachment uploads may fail silently Attachment uploads are processed asynchronously by Airtable. As a result, a mutation may succeed even if one or more attachments fail to upload. In these cases, Airtable may either remove the attachment from the record or store it with a URL that later returns an error. Only after repeated failures may Airtable return an explicit `ATTACHMENTS_FAILED_UPLOADING` error. ::callout{color="warning" icon="i-lucide-triangle-alert"} A successful mutation response does not guarantee that every attachment was stored correctly. This Airtable limitation cannot be detected by the Mutation Engine. :: ## Get help If you encounter issues or unexpected behavior, feel free to [get in touch](https://onderwijsin.nl/contact){rel=""nofollow""}. We are happy to help troubleshoot and assist with your integration. # Upgrade guide A step-by-step checklist for migrating from Version 1 to Version 2. Even though this is a major release, most migrations are straightforward. If your integration only submits new candidates, upgrading typically takes **less than 10 minutes**. ## Who this guide is for This guide is intended for developers migrating an existing v1 integration to v2. Migration effort depends on your use case: - **Easy (\~10 minutes):** You only create candidates and do not rely on Version 1 schema parsing. - **Moderate (\~30–60 minutes):** You update candidates, submit interactions, or dynamically map fields. - **Advanced (1–2+ hours):** You generate forms or validation logic from the Version 1 `/schema` response, or assume synchronous writes. ## Overview Version 2 introduces many new features with only a small number of breaking changes. Most breaking changes are the result of **stricter naming conventions** and can be resolved by updating property names. The most important conceptual change—though not strictly breaking—is the introduction of the **Mutation Engine**. All write operations are now asynchronous. Integrating this properly may require small changes to your application flow. We strongly recommend reviewing the Mutation Engine documentation before starting your migration. [Read the Mutation Engine guide](https://docs.onderwijsregio.onderwijsin.nl/mutations/mutation-engine) before starting your migration. ## Handle asynchronous writes All create, update, and delete operations in v2: - Return `202 Accepted`. - Are processed asynchronously. - Complete through callbacks or eventual consistency. If your v1 integration assumed immediate write results, you must update your logic. ## Mandatory steps Complete all steps in this section to avoid breaking changes in your application. ### Change the base URL The new API is accessible at: ```text https://onderwijsregio.onderwijsin.nl/api/v2 ``` All endpoints referenced in the documentation are relative to this base URL. ### Change the authentication header The old `api-key` header is deprecated. Use the `x-api-key` header instead. ### Map candidate field keys The field names in the candidate schema have been translated from Dutch to English, along with several additional standardization changes. From **version 1.1** onward, the `/create-user` endpoint returned the newly created record exactly as defined in Airtable, using Airtable’s record field keys. These keys differed from the field keys returned by the `/schema` endpoint. In **version 2**, all field keys have been fully standardized for both **input and output**. This ensures consistency across endpoints. Refer to the table below for the new field key mappings. | | | | | ---------------- | --------------------- | --------------------- | | **v1** | **Airtable (v1)** | **v2** | | naam | Naam | name | | email | Email | email | | privacy\_consent | Privacy Consent | privacyConsent | | telefoon | Telefoon | phone | | fase | Fase | phase | | sector | Sector voorkeur | sectorPreferences | | functie | Functie voorkeur | rolePreferences | | kwalificatie | Gewenste kwalificatie | desiredQualifications | | vooropleiding | Vooropleiding | priorEducation | | - | - | subjectPreferences | | linkedin | LinkedIn | linkedin | | geboortedatum | Geboortedatum | dateOfBirth | | motivatie | Motivatie | motivation | | adres | Adres | address | | plaats | Plaatsnaam | city | | postcode | Postcode | postalCode | | - | - | curriculumVitae | | - | - | otherAttachments | | interacties | interacties | interactions | | interactie | interacties | *deprecated* | | custom\_fields | - | customFields | ::note Custom fields in `customFields` are user-generated and dynamic, so they have **not** been renamed. Naming conventions cannot be enforced for them. :: As in version 1, you should introspect the (custom) fields schema via the `/schema` endpoint to retrieve the available custom field mappings. ### Map interaction field keys Similar to the candidate schema, the field keys for **interactions** have also been renamed and standardized in version 2. Refer to the table below for the updated interaction field mappings. | | | | | ------------ | ----------------- | ----------- | | **v1** | **Airtable (v1)** | **v2** | | datum | Uitgevoerd op | performedAt | | soort | Soort | type | | samenvatting | Samenvatting | summary | | - | Notities | notes | | - | Duur | duration | ### Use the new custom-fields interface Custom fields are now modeled as a **nested JSON Schema object** under `customFields`. They: - Use the same JSON Schema constructs as shared fields - Are region-specific and dynamically generated - Must always be discovered via `/schema` - Are validated only when known (see custom field validation behavior) This replaces the legacy `objectArray`-based custom field modeling from v1. ### Complete the checklist - Change the base URL. - Change the authentication header. - Map candidate field keys. - Map interaction field keys. - Change the custom-fields interface. --- ## Optional steps These steps are optional but recommended to take advantage of new features and improvements. ### Update schema handling In version 2, the `/schema` endpoint has been **fundamentally redesigned**. While the goal of the endpoint remains the same—describing which candidate payloads are accepted—the **schema is no longer a custom, proprietary format**. Instead, it now returns a **fully standards-compliant JSON Schema** that directly mirrors API validation. This is one of the most important improvements in v2. This section highlights the most important differences and what you need to adapt when migrating from v1 to v2. #### Compare the schema formats In v1, the `/schema` endpoint returned a **custom field-description format** that required client-side interpretation. In v2, the `/schema` endpoint returns: - A **standards-compliant JSON Schema document** - Fully compatible with **OpenAPI** - Generated directly from the API’s **Zod validation schemas** - Guaranteed to stay in sync with runtime validation There is no longer any difference between: - What the schema describes - What the API accepts - What the API validates Clients that rely on the /schema response for validation, form generation, or dynamic payload construction must update their schema parsing logic when upgrading to v2. #### Understand the old schema In version 1, the schema: - Used a **custom structure** (`fieldName`, `fieldType`, `options`, etc.) - Required clients to interpret field semantics manually - Modeled nested data using ad-hoc conventions (`objectArray`) - Was **not directly usable** with JSON Schema or OpenAPI tooling - Could drift from actual API validation logic Example characteristics: - Types expressed as loose strings - Required fields inferred rather than enforced - No formal validation rules (patterns, formats, unions) - Limited tooling support #### Use the new schema In version 2, the `/schema` endpoint returns a **complete JSON Schema document**. Key properties: - Uses standard JSON Schema keywords: - `type` - `properties` - `required` - `enum` - `format` - `oneOf` / `anyOf` - `additionalProperties` - Explicitly defines: - Required vs optional fields - Allowed values - Nested object and array structures - Fully compatible with: - OpenAPI tooling - JSON Schema validators - Form generators - AI structured-output systems The schema uses [Zod’s native JSON Schema support](https://zod.dev/json-schema){rel=""nofollow""}. You can create your own validation logic with `z.fromJSONSchema()`. This ensures the schema is **authoritative** and **cannot drift** from API validation. ### Integrate the sandbox environment For non-production environments, you can now use the sandbox environment. The sandbox provides access to the same API features as production but is fully isolated from production data. You can use the same API keys as usual. To enable the sandbox, use one of the following approaches: - Prefix the endpoint with `/sandbox` instead of `/v2`. Example: ```text https://onderwijsregio.onderwijsin.nl/api/sandbox/schema ``` - Set the `x-api-sandbox` header to `true` ### Explore the new endpoints in the API Explorer Version 2 introduces dozens of endpoints. Explore, inspect, and test them in the [interactive API reference](https://docs.onderwijsregio.onderwijsin.nl/api-reference). # Compliance An overview of data processing and sub-processors. This platform is part of a shared digital infrastructure used by education regions to manage candidate journeys, collaboration data, and reporting. Because integrations often involve handling personal data, it is important for developers and technical stakeholders to understand **what data is processed** and **which infrastructure providers are involved**. Two formal annexes have been created as part of the data processing agreement (DPA) that accompanies the platform. ## Annex 1: Categories of processed data This annex provides a structured overview of: - Categories of data subjects, such as candidates, users, and contacts. - Types of personal data processed. - Purposes of processing. - The role of region-specific custom fields. It reflects the actual data model exposed through the API (including candidate payloads, interactions, and configuration data) and should be consulted when designing integrations that store or transmit personal data. ::u-button --- target: _blank to: https://onderwijsin.notion.site/Overzicht-verwerking-persoonsgegevens-30ab66e1be69807382f7cd3302dd129a trailing-icon: i-lucide-arrow-up-right --- Read Annex 1 :: ## Annex 2: Sub-processors This annex lists the infrastructure providers that process data on behalf of the platform, including: - The primary data platform used for storage and collaboration. - The infrastructure that runs the API and its supporting services. - Each provider’s purpose and scope of involvement. - Data-hosting locations and transfer considerations. Developers should be aware of these dependencies when performing security reviews, DPIAs, or internal compliance checks. ::u-button --- target: _blank to: https://onderwijsin.notion.site/Overzicht-subverwerkers-30ab66e1be698096a970eff040caaa98 trailing-icon: i-lucide-arrow-up-right --- Read Annex 2 :: # Getting started Things to know before making your first API request. ::callout{color="info" icon="i-lucide-info"} Version 1 is legacy documentation. Use Version 2 for new integrations. :: This API is part of a [shared digital infrastructure](https://onderwijsin.nl/projecten/digitale-infrastructuur-onderwijsregios){rel=""nofollow""}, developed by and for education regions (*onderwijsregio’s*). It supports prospective teachers and education professionals from orientation through their start in education. The core system is built on [Airtable](https://airtable.com){rel=""nofollow""} and functions as an *Applicant Tracking System* (ATS) and CRM for regional coordination, data sharing, and reporting. To integrate this infrastructure with regional websites and third-party applications, we provide an API layer that enables external systems to submit data in a structured and secure way. This documentation is intended for developers building or maintaining integrations with the platform and describes the available endpoints, data models, and integration patterns. ## Access and authentication Access to the API is granted via an API key. For security and governance reasons, API keys are only issued to education regions that participate in the project. An API key must be requested by an employee of the relevant education region. We do not issue API keys directly to third-party vendors or external developers without involvement and approval from the region itself. Once approved, the region is responsible for managing the API key and coordinating its use with any third-party applications or integration partners. The API key must be included with each request as described in the authentication section of this documentation. ## Why not provide direct access to the Airtable API? We intentionally do not provide direct access to the underlying Airtable API. The primary reason is data separation. The Airtable base is shared across multiple education regions, and direct access would make it possible to view or modify data belonging to other regions, which is not acceptable from a privacy and governance perspective. Additionally, the Airtable setup relies on strict internal conventions and constraints. While Airtable’s flexibility is a strength, it also makes it easy to unintentionally break data structures or workflows. The API layer acts as a controlled abstraction, enforcing validation and business logic to protect data integrity and system stability across all regions. # Authentication Learn how to authenticate your requests to the API. The API uses simple API key–based authentication. Each education region is issued a single API key, which must be included in every request via the `api-key` HTTP header. API keys do not expire. The region that receives the key is responsible for its secure storage and use, including any third-party integrations that rely on it. A new major version of the API (v2) is planned for early 2026 and will include a redesign of the authentication system. Existing API keys will remain valid after migrating to v2. ## Examples ::code-group ```ts [TypeScript] const response = await fetch( "https://onderwijsregio.onderwijsin.nl/api/v1/schema", { method: "GET", headers: { "api-key": process.env.ONDERWIJSREGIO_API_KEY as string, "Content-Type": "application/json", }, } ); const data = await response.json(); ``` ```php [PHP] $ch = curl_init("https://onderwijsregio.onderwijsin.nl/api/v1/schema"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "api-key: " . getenv("ONDERWIJSREGIO_API_KEY"), "Content-Type: application/json", ], ]); $response = curl_exec($ch); curl_close($ch); ``` ```python [Python] import os import requests response = requests.get( "https://onderwijsregio.onderwijsin.nl/api/v1/schema", headers={ "api-key": os.environ["ONDERWIJSREGIO_API_KEY"], "Content-Type": "application/json", }, ) data = response.json() ``` ```bash [cURL] curl https://onderwijsregio.onderwijsin.nl/api/v1/schema \ -H "api-key: $ONDERWIJSREGIO_API_KEY" \ -H "Content-Type: application/json" ``` :: # Schema Get a clear overview of the shared candidate data model and the region-specific custom fields that apply to your education region. Before creating a candidate via the `/create-user` endpoint, you should retrieve the schema for your region using the `/schema` endpoint. This endpoint returns the complete, region-specific definition of all fields that are accepted in the candidate payload. The schema is authoritative: it describes which fields are available, which are required, which values are allowed, and how nested or related data must be structured. You should always base your payload construction and validation on the response of this endpoint. ## Use the `/schema` endpoint The schema response is tailored to the authenticated region and consists of three main parts: 1. **Shared fields** 2. **Interactions** 3. **Custom fields** Together, these define the full shape of the payload accepted by `/create-user`. ## Shared fields Shared fields are standardized across all education regions. They represent the core candidate data model and are always present in the schema. Each shared field definition includes: - `fieldName`: the name to use in the request payload. - `fieldType`: the expected type, such as `string`, `array`, or `boolean`. - `required`: whether the field is mandatory. - `options` (optional): allowed values for enumerated fields. - `fieldDescription`: a human-readable explanation of the field. Examples of shared fields include *candidate identity data*, *consent flags*, *current phase*, *sector preferences*, *qualifications*, and *contact details*. If a field defines an `options` array, the provided value must match one of the allowed options exactly. Required shared fields must always be present in the payload. Optional fields may be omitted entirely. ## Interactions Interactions represent contact moments or events related to a candidate, such as phone calls, emails, or attendance. Interactions are a related resource, but they can be created as part of the same payload when creating or updating a candidate. The schema supports: - A single interaction object - An array of interaction objects Each interaction definition specifies: - Required properties (such as *date* and *type of interaction*) - Allowed interaction types - Optional descriptive fields (such as a summary) The schema still exposes a legacy single-interaction field (`interactie`) for backward compatibility. Prefer the plural form, `interacties`. ## Custom fields Custom fields are region-specific extensions to the shared schema. They allow each education region to capture additional data that is not standardized across all regions. Key characteristics of custom fields: - They are returned as a sub-schema inside the `custom_fields` definition. - They are dynamically generated from the authenticated API key. - Their structure and allowed values can differ by region. In the payload, custom fields are always sent as an array of objects with: - `field_name`: the exact name of the custom field as defined in the schema - `field_value`: the value for that field The schema defines, per custom field: - Whether the field is required - The expected data type - Allowed options (if applicable) - A description of its intended use Your integration should treat the custom fields schema as dynamic and must not assume a fixed set of custom fields across regions. ## How to use the schema The schema is primarily intended as a development and discovery tool, allowing you to explore the data model and available custom fields for your region. While it can technically be used at runtime to dynamically construct payloads, this is not a strict requirement. Validation of custom fields is intentionally lenient. If your payload contains custom field entries or properties that are not defined in the schema, they will be ignored rather than causing the request to fail. Custom fields for a region change infrequently, and sending invalid or outdated custom fields will not break your request. # Submitting a candidate Learn how to create or update a candidate within the applicant tracking system (ATS) of your education region. This guide explains how to call the `/create-user` endpoint, how to structure the payload, and how the API processes candidate data. ## Endpoint overview To submit a candidate, send a `POST` request to: ```http https://onderwijsregio.onderwijsin.nl/api/v1/create-user ``` Each request must include: - An `api-key` header - A request body encoded as `application/json` ## Upsert behavior As of **API version 1.1.0**, the `/create-user` endpoint uses **upsert logic**. - The candidate’s **email address** is used as the unique identifier. - If a candidate with the same email already exists, the existing record is updated. - If multiple records with the same email exist, **only the first match is updated**. This allows you to safely resend data for the same candidate without creating duplicates. ## Request payload The request body contains the candidate data. The minimum required fields are: - `naam` - `email` - `privacy_consent` All other fields are optional and may be included depending on your use case and region configuration. The payload must conform to the schema returned by the `/schema` endpoint. ## Supported field values Some fields only accept predefined values. The most important standardized options are listed below. ::code-group ```text [Phases] nieuw matchen oriënteren voorbereiden in opleiding werkzaam uitgestroomd ``` ```text [Sectors] Primair onderwijs Voortgezet onderwijs Speciaal onderwijs Middelbaar beroepsonderwijs Hoger onderwijs Praktijkonderwijs ``` ```text [Roles] Docent / leraar Instructeur (mbo) OOP Ondersteuning Leerlingenzorg Midden management Schoolleiding Anders ``` ```text [Qualifications] Primair onderwijs Beperkt tweedegraads Tweedegraads Eerstegraads PDG BKO/BDB Kwalificatie onderwijs ondersteunend personeel Kwalificatie Instructeur (MBO) Geen: gastdocentschap Pabo Onbekend Nog geen idee Niet van toepassing ``` ```text [Prior education] geen praktijkonderwijs vmbo-bl vmbo-kl vmbo-gl vmbo-tl vmbo havo vwo mbo entree mbo 2 mbo 3 mbo 4 mbo associate degree hbo propedeuse hbo bachelor hbo master hbo wo bachelor wo master wo PhD ``` ```text [Interaction types] Telefonisch E-mail Persoonlijk Anders Doorverwijzing Adviesgesprek Kennismakingsgesprek Inschrijving activiteit Niet van toepassing ``` :: Always refer to the `/schema` endpoint for the authoritative and most up-to-date list of allowed values. ## Interactions You can optionally include one or more interactions when submitting a candidate. This is useful when a candidate submits their details as part of an activity or contact moment. - Interactions can be sent as a single object or an array. - Each interaction requires at least a date and one or more interaction types. A legacy single-interaction field is still supported for backward compatibility. It is preferred to use the `interacties` property. ## Custom fields In addition to the shared schema, regions may define custom fields. These fields are unique per region and are sent as an array under `custom_fields`. Each custom field consists of: - `field_name`: the exact name from the schema - `field_value`: the value for that field If your payload contains custom fields that are not defined for your region, they are ignored and do not cause the request to fail. Valid custom fields in the same payload are still processed. Use the `/schema` endpoint to discover which custom fields are available for your region. ## Example request ```http POST https://onderwijsregio.onderwijsin.nl/api/v1/create-user Content-Type: application/json api-key: ``` ```json [request.json] { "naam": "Pietje Puk", "email": "pietjepuk@onderwijsin.nl", "privacy_consent": true, "fase": "nieuw", "interacties": { "datum": "2024-07-05", "soort": ["Inschrijving activiteit"], "samenvatting": "Aanmelding voor informatieavond" }, "custom_fields": [ { "field_name": "Voorkeur vak", "field_value": "Wiskunde" } ] } ``` ## Responses ### Unauthorized ```json [response.json] { "statusCode": 401, "statusMessage": "Not authorized", "message": "Invalid api key" } ``` ### Invalid payload ```json [response.json] { "statusCode": 400, "statusMessage": "Bad request", "message": "Please check your request body.", "data": { "errors": ["Detailed validation errors"] } } ``` ### Server error ```json [response.json] { "statusCode": 500, "statusMessage": "Server error", "message": "Something went wrong creating the record. Please try again." } ``` ### Success ::code-collapse ```json [response.json] { "statusCode": 200, "message": "Candidate was created successfully", "data": { "candidate": { "id": "recGHIjkl12345", "name": "Test", "email": "test@onderwijsin.nl", "phase": "nieuw", "privacy_consent": true, "regions": { "count": 1, "ids": ["recABCdef12345"], "origin": ["recABCdef12345"] }, "timestamps": { "created_at": "2025-12-19T16:57:19.000Z", "original_created_at": "2025-12-19T16:57:19.136Z", "updated_at": "2025-12-19T17:10:49.000Z" }, "created_by": { "id": "usrABCdef12345", "name": "Onderwijs in", "email": "jacob@onderwijsin.nl" }, "updated_by": { "id": "usrABCdef12345", "name": "Onderwijs in", "email": "jacob@onderwijsin.nl" }, "custom_fields": { "vakken": ["wiskunde"] } }, "interactions": [ { "name": "Telefonisch - 2023-01-01", "type": ["Telefonisch"], "summary": "Ingeschreven voor de activiteit XYZ", "performed_at": "2023-01-01T00:00:00.000Z", "created_at": "2025-12-19T22:42:59.000Z", "created_by": { "id": "usrABCdef12345", "name": "Onderwijs in", "email": "jacob@onderwijsin.nl" } } ] } } ``` :: # Troubleshooting This section describes known issues and recommended workarounds when integrating with the API. ## Handle variable response times The API depends on the Airtable API, which can exhibit significant latency with large variation. We believe this is likely caused by cold starts on Airtable’s side, though this has not been fully confirmed. - **Mutation requests** typically complete within **500–1000 ms**, but can take **up to 20 seconds** in rare cases. - **Schema requests** involve database introspection and usually take **3–8 seconds** on the first request. For read-only fetch requests, the API applies a caching layer. After the initial request, subsequent calls are significantly faster. ::callout{color="warning" icon="i-lucide-clock-3"} Configure client-side timeouts of **at least 15 seconds** , and preferably longer, to avoid failures caused by upstream latency. :: Improving response time consistency is a core focus for the next major API release. ## Get help If you encounter issues or unexpected behavior, feel free to [get in touch](https://onderwijsin.nl/contact){rel=""nofollow""}. We are happy to help troubleshoot and assist with your integration. # Onderwijsregio API Onderwijsregio API 2.0.0 This API is part of a [shared digital infrastructure](https://onderwijsin.nl/projecten/digitale-infrastructuur-onderwijsregios/) developed by and for education regions (onderwijsregio’s) to track and support prospective teachers and other education professionals throughout their journey—from initial orientation to their start in education. Remi Huigen {rel=""nofollow""} {rel=""nofollow""} # DELETE /auth/regions/api-keys/{id} DELETE /auth/regions/api-keys/{id} deleteRegionApiKey Delete API key Deletes an API key for the specified region. Deletion is prevented if it is the only API key remaining for the region. Regions Auth id path Unique identifier of the API key. region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. API key deleted successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data result ApiKey id Internal unique identifier for the API key. tokenPreview Short preview of the API key used for identification (non-sensitive). description Optional description of what the API key is used for. regionId Identifier of the region this API key belongs to. createdAt Timestamp when the API key was created. updatedAt Timestamp when the API key was last updated. Invalid request or deletion would leave the region without any API keys Error statusCode statusMessage message data Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Requested resource does not exist Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # DELETE /candidates/{id} DELETE /candidates/{id} deleteCandidate Delete candidate Permanently deletes a candidate by ID. The record will no longer be included in historical reporting and cannot be restored. **Warning:** In most cases you should archive a candidate instead. Candidates id path Unique identifier of the candidate. callbackUrl query Optional URL to notify upon mutation completion with the result. idempotencyKey query Client-supplied idempotency key to prevent duplicate mutations. region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. Mutation Engine accepted request for asynchronous processing SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. statusCode HTTP status code indicating that the mutation has been accepted for processing. data MutationEngineAccepted status The current status of the mutation request. mutationId Unique identifier for the mutation request. idempotencyKey Idempotency key provided by client that is associated with the mutation request. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Requested resource does not exist Error statusCode statusMessage message data # DELETE /candidates/{id}/interactions/{interactionId} DELETE /candidates/{id}/interactions/{interactionId} deleteInteraction Delete an interaction Deletes an interaction by its ID. Candidates Interactions id path Unique identifier of the candidate. interactionId path Unique identifier of the interaction. callbackUrl query Optional URL to notify upon mutation completion with the result. idempotencyKey query Client-supplied idempotency key to prevent duplicate mutations. region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. Mutation Engine accepted request for asynchronous processing SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. statusCode HTTP status code indicating that the mutation has been accepted for processing. data MutationEngineAccepted status The current status of the mutation request. mutationId Unique identifier for the mutation request. idempotencyKey Idempotency key provided by client that is associated with the mutation request. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data # DELETE /regions/{id} DELETE /regions/{id} deleteRegion Delete a region Deletes a region. **Admin-only endpoint.** Regions Admin id path Internal identifier of the region. This is **not the same as Airtable record id** Region deleted successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data Region id Internal unique identifier for the region. recordId Airtable record ID identifying the region. baseId Airtable base ID the region belongs to. name Name of the region. status Current status of the region. administrativeEmail Primary administrative contact email for the region. techSupportEmail Technical support contact email for the region. createdAt Timestamp when the region was created. updatedAt Timestamp when the region was last updated. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # GET /auth/candidates/me GET /auth/candidates/me getAuthenticatedCandidate Get authenticated candidate Returns information about the currently authenticated candidate based on the active session. Auth Candidates Authenticated candidate data retrieved successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data session Session id Unique identifier of the session. createdAt Timestamp when the session was created. expiresAt Timestamp when the session expires. records Candidate records linked to the authenticated session. Candidate Candidate record as returned by the API. CandidateBase Base candidate fields shared across create, update, and output variants. name The full name of the candidate. email The primary email address of the candidate. privacyConsent Indicates whether the candidate has given consent for data processing. phone The primary phone number of the candidate. linkedin The LinkedIn profile URL of the candidate. dateOfBirth The date of birth of the candidate. address The residential address of the candidate. city The city where the candidate resides. postalCode The postal code of the candidate's address. phase Candidate phase Phase the candidate is currently in. sectorPreferences Educational sectors the candidate is interested in. Sector Educational sector. rolePreferences Roles, jobs, or positions in education the candidate is interested in. Role Role, job, or position in education. desiredQualifications Qualifications that the candidate wants to obtain. Qualification Qualification that the candidate wants to obtain. priorEducation Prior education The highest level of education the candidate has completed. subjectPreferences Subjects that the candidate is interested in. Subject Subject that the candidate is interested in. motivation Motivation letter or explanation from the candidate. id Unique identifier of the candidate record. curriculumVitae Curriculum vitae attachments stored in Airtable. AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails small Thumbnail url width height large Thumbnail url width height full Thumbnail url width height otherAttachments Other attachments stored in Airtable. AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails notes Internal notes about the candidate. Only visible to staff, not to the candidate. archived Indicates whether the candidate is archived. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # GET /auth/regions/me GET /auth/regions/me getAuthenticatedRegion Get authenticated region Returns information about the currently authenticated region. Admin-scoped API keys may use the optional region query parameter to simulate requests for a specific region. Regions Auth region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. Authenticated region data retrieved successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data RegionWithApiKeys Region id Internal unique identifier for the region. recordId Airtable record ID identifying the region. baseId Airtable base ID the region belongs to. name Name of the region. status Current status of the region. administrativeEmail Primary administrative contact email for the region. techSupportEmail Technical support contact email for the region. createdAt Timestamp when the region was created. updatedAt Timestamp when the region was last updated. apiKeys API keys associated with the region. ApiKey id Internal unique identifier for the API key. tokenPreview Short preview of the API key used for identification (non-sensitive). description Optional description of what the API key is used for. regionId Identifier of the region this API key belongs to. createdAt Timestamp when the API key was created. updatedAt Timestamp when the API key was last updated. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # GET /candidates GET /candidates listCandidates List candidates Lists candidates belonging to the authenticated region. Results are paginated using a cursor-based pagination strategy. Candidates pageSize query Number of items per page. Defaults to 10. cursor query Pagination cursor returned by a previous request. fields query List of candidate fields to return. Defaults to all standard shared fields (excluding interactions and custom fields). x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. Candidates retrieved successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data Candidate Candidate record as returned by the API. CandidateBase Base candidate fields shared across create, update, and output variants. name The full name of the candidate. email The primary email address of the candidate. privacyConsent Indicates whether the candidate has given consent for data processing. phone The primary phone number of the candidate. linkedin The LinkedIn profile URL of the candidate. dateOfBirth The date of birth of the candidate. address The residential address of the candidate. city The city where the candidate resides. postalCode The postal code of the candidate's address. phase Candidate phase Phase the candidate is currently in. sectorPreferences Educational sectors the candidate is interested in. Sector Educational sector. rolePreferences Roles, jobs, or positions in education the candidate is interested in. Role Role, job, or position in education. desiredQualifications Qualifications that the candidate wants to obtain. Qualification Qualification that the candidate wants to obtain. priorEducation Prior education The highest level of education the candidate has completed. subjectPreferences Subjects that the candidate is interested in. Subject Subject that the candidate is interested in. motivation Motivation letter or explanation from the candidate. id Unique identifier of the candidate record. curriculumVitae Curriculum vitae attachments stored in Airtable. AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails small Thumbnail url width height large Thumbnail url width height full Thumbnail url width height otherAttachments Other attachments stored in Airtable. AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails notes Internal notes about the candidate. Only visible to staff, not to the candidate. archived Indicates whether the candidate is archived. meta BaseResponseMeta sandboxEnabled PaginationMeta pageSize Number of items requested per page. cursor Cursor for fetching the next page. Null when there are no more results. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # GET /candidates/find GET /candidates/find findCandidates Find candidates Finds candidates within the authenticated region by email address or phone number. At least one of `email` or `phone` must be provided. If both email and phone are provided, email is used for the search. Candidates email query Email address to search for. Case-insensitive. phone query Phone number to search for. All non-numeric characters are ignored. fields query List of candidate fields to return. Defaults to all standard shared fields (excluding interactions and custom fields). x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. Candidates retrieved successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data Candidate Candidate record as returned by the API. CandidateBase Base candidate fields shared across create, update, and output variants. name The full name of the candidate. email The primary email address of the candidate. privacyConsent Indicates whether the candidate has given consent for data processing. phone The primary phone number of the candidate. linkedin The LinkedIn profile URL of the candidate. dateOfBirth The date of birth of the candidate. address The residential address of the candidate. city The city where the candidate resides. postalCode The postal code of the candidate's address. phase Candidate phase Phase the candidate is currently in. sectorPreferences Educational sectors the candidate is interested in. Sector Educational sector. rolePreferences Roles, jobs, or positions in education the candidate is interested in. Role Role, job, or position in education. desiredQualifications Qualifications that the candidate wants to obtain. Qualification Qualification that the candidate wants to obtain. priorEducation Prior education The highest level of education the candidate has completed. subjectPreferences Subjects that the candidate is interested in. Subject Subject that the candidate is interested in. motivation Motivation letter or explanation from the candidate. id Unique identifier of the candidate record. curriculumVitae Curriculum vitae attachments stored in Airtable. AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails small Thumbnail url width height large Thumbnail url width height full Thumbnail url width height otherAttachments Other attachments stored in Airtable. AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails notes Internal notes about the candidate. Only visible to staff, not to the candidate. archived Indicates whether the candidate is archived. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # GET /candidates/{id} GET /candidates/{id} getCandidateById Get candidate by ID Retrieves a single candidate by ID within the authenticated region. Returned fields can be limited using the `fields` query parameter. By default, all standard shared fields are returned, excluding interactions and custom fields. Interactions can be optionally included. Candidates id path Unique identifier of the candidate. fields query List of candidate fields to return. Defaults to all standard shared fields (excluding interactions and custom fields). includeInteractions query Whether to include interaction records linked to the candidate. Interactions are only included when the request is scoped to a region. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. Candidate retrieved successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data Candidate Candidate record as returned by the API. CandidateBase Base candidate fields shared across create, update, and output variants. name The full name of the candidate. email The primary email address of the candidate. privacyConsent Indicates whether the candidate has given consent for data processing. phone The primary phone number of the candidate. linkedin The LinkedIn profile URL of the candidate. dateOfBirth The date of birth of the candidate. address The residential address of the candidate. city The city where the candidate resides. postalCode The postal code of the candidate's address. phase Candidate phase Phase the candidate is currently in. sectorPreferences Educational sectors the candidate is interested in. Sector Educational sector. rolePreferences Roles, jobs, or positions in education the candidate is interested in. Role Role, job, or position in education. desiredQualifications Qualifications that the candidate wants to obtain. Qualification Qualification that the candidate wants to obtain. priorEducation Prior education The highest level of education the candidate has completed. subjectPreferences Subjects that the candidate is interested in. Subject Subject that the candidate is interested in. motivation Motivation letter or explanation from the candidate. id Unique identifier of the candidate record. curriculumVitae Curriculum vitae attachments stored in Airtable. AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails small Thumbnail url width height large Thumbnail url width height full Thumbnail url width height otherAttachments Other attachments stored in Airtable. AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails notes Internal notes about the candidate. Only visible to staff, not to the candidate. archived Indicates whether the candidate is archived. CandidateWithInteractions Candidate Candidate record as returned by the API. interactions Interaction records linked to the candidate. Interaction Interaction record as returned by the API. InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. id Unique identifier of the interaction record. name Human-readable name of the interaction. createdAt Datetime when the interaction record was created. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Requested resource does not exist Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # GET /candidates/{id}/interactions GET /candidates/{id}/interactions listCandidateInteractions List candidate interactions Lists interaction records belonging to a specific candidate within the authenticated region. Candidates Interactions id path Unique identifier of the candidate. region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. Interactions retrieved successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data Interaction Interaction record as returned by the API. InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. id Unique identifier of the interaction record. name Human-readable name of the interaction. createdAt Datetime when the interaction record was created. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Requested resource does not exist Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # GET /candidates/{id}/interactions/{interactionId} GET /candidates/{id}/interactions/{interactionId} getCandidateInteractionById Get interaction by ID Retrieves a single interaction record by its ID within the authenticated region. Candidates Interactions id path Unique identifier of the candidate. interactionId path Unique identifier of the interaction. region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. Interaction retrieved successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data Interaction Interaction record as returned by the API. InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. id Unique identifier of the interaction record. name Human-readable name of the interaction. createdAt Datetime when the interaction record was created. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Requested resource does not exist Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # GET /regions GET /regions listRegions List all regions Returns all regions along with their associated API keys. **Admin-only endpoint.** Regions Admin Regions retrieved successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data RegionWithApiKeys Region id Internal unique identifier for the region. recordId Airtable record ID identifying the region. baseId Airtable base ID the region belongs to. name Name of the region. status Current status of the region. administrativeEmail Primary administrative contact email for the region. techSupportEmail Technical support contact email for the region. createdAt Timestamp when the region was created. updatedAt Timestamp when the region was last updated. apiKeys API keys associated with the region. ApiKey id Internal unique identifier for the API key. tokenPreview Short preview of the API key used for identification (non-sensitive). description Optional description of what the API key is used for. regionId Identifier of the region this API key belongs to. createdAt Timestamp when the API key was created. updatedAt Timestamp when the API key was last updated. Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # GET /schema GET /schema getCandidateSchema Get candidate JSON Schema Returns a region-specific, standards-compliant JSON Schema describing the request body for creating or updating a candidate. The schema is generated dynamically from the same validation logic used by the API and can be used for validation, form generation, or structured outputs. Schema operation query The candidate operation for which to retrieve the schema. target query Target JSON Schema specification to generate. region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. Candidate JSON Schema retrieved successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data CandidateRequestJsonSchema A standards-compliant JSON Schema document describing the request payload for creating or updating a candidate. $schema The JSON Schema specification identifier. type Root type of the candidate request payload. properties Property definitions for the candidate payload. Each key maps to a JSON Schema describing that field. required List of required top-level fields for the selected operation. additionalProperties Whether additional properties outside the defined schema are allowed. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # PATCH /candidates/{id} PATCH /candidates/{id} updateCandidate Update a candidate Updates an existing candidate by ID. All fields in the request body are optional, but at least one field must be provided. Inline creation of interactions is not supported via this endpoint. For region-based authentication, ownership of the candidate is not pre-validated at request time. If the candidate does not belong to the region, the mutation engine will reject the operation and propagate the error to the callback URL if provided. Candidates id path Unique identifier of the candidate. callbackUrl query Optional URL to notify upon mutation completion with the result. idempotencyKey query Client-supplied idempotency key to prevent duplicate mutations. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. CandidateUpdate Payload used to update an existing candidate. At least one field must be provided. CandidateBase Base candidate fields shared across create, update, and output variants. name The full name of the candidate. email The primary email address of the candidate. privacyConsent Indicates whether the candidate has given consent for data processing. phone The primary phone number of the candidate. linkedin The LinkedIn profile URL of the candidate. dateOfBirth The date of birth of the candidate. address The residential address of the candidate. city The city where the candidate resides. postalCode The postal code of the candidate's address. phase Candidate phase Phase the candidate is currently in. sectorPreferences Educational sectors the candidate is interested in. Sector Educational sector. rolePreferences Roles, jobs, or positions in education the candidate is interested in. Role Role, job, or position in education. desiredQualifications Qualifications that the candidate wants to obtain. Qualification Qualification that the candidate wants to obtain. priorEducation Prior education The highest level of education the candidate has completed. subjectPreferences Subjects that the candidate is interested in. Subject Subject that the candidate is interested in. motivation Motivation letter or explanation from the candidate. curriculumVitae Updated curriculum vitae attachments. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. Attachment via URL filename The name of the file including its extension. url Public URL where the file can be accessed. Attachment via base64 data filename The name of the file including its extension. data Base64-encoded file contents including the data URI prefix. otherAttachments Updated other attachments. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. customFields StaticCustomFields Custom candidate fields defined per region. \_notes **Admin-only field.** Internal notes about the candidate. This field can only be set with an admin API key. Notes are not visible to the candidate. \_archived **Admin-only field.** Indicates whether the candidate is archived. This field can only be set with an admin API key. Use the `/archive` endpoint for non-admin access. Mutation Engine accepted request for asynchronous processing SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. statusCode HTTP status code indicating that the mutation has been accepted for processing. data MutationEngineAccepted status The current status of the mutation request. mutationId Unique identifier for the mutation request. idempotencyKey Idempotency key provided by client that is associated with the mutation request. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data # PATCH /candidates/{id}/interactions/{interactionId} PATCH /candidates/{id}/interactions/{interactionId} updateInteraction Update an interaction Updates an existing interaction by its ID. At least one field must be provided in the request body. Candidates Interactions id path Unique identifier of the candidate. interactionId path Unique identifier of the interaction. callbackUrl query Optional URL to notify upon mutation completion with the result. idempotencyKey query Client-supplied idempotency key to prevent duplicate mutations. region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. InteractionUpdate Payload used to update an interaction. InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. Mutation Engine accepted request for asynchronous processing SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. statusCode HTTP status code indicating that the mutation has been accepted for processing. data MutationEngineAccepted status The current status of the mutation request. mutationId Unique identifier for the mutation request. idempotencyKey Idempotency key provided by client that is associated with the mutation request. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data # PATCH /regions/{id} PATCH /regions/{id} updateRegion Update a region Partially updates a region. **Admin-only endpoint.** Regions Admin id path Internal identifier of the region. This is **not the same as Airtable record id** RegionUpdate RegionEditable name The name of the region administrativeEmail Contact email for administrative issues techSupportEmail Contact email for technical support. This is usually the person utilizing the API. Region updated successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data Region id Internal unique identifier for the region. recordId Airtable record ID identifying the region. baseId Airtable base ID the region belongs to. name Name of the region. status Current status of the region. administrativeEmail Primary administrative contact email for the region. techSupportEmail Technical support contact email for the region. createdAt Timestamp when the region was created. updatedAt Timestamp when the region was last updated. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # POST /auth/candidates/request-email-verification POST /auth/candidates/request-email-verification requestCandidateEmailVerification Request email verification Initiates an email-based identity verification flow for a candidate. If the email address matches one or more candidates, a verification link is sent to the email address. Auth email query Email address used to verify the candidate's identity. Verification email sent successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Requested resource does not exist Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # POST /auth/regions/api-keys POST /auth/regions/api-keys createRegionApiKey Create new API key Creates a new API key for the specified region. The full API key is returned **only once** and must be stored securely. Subsequent requests will only expose a preview. Regions Auth region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. ApiKeyCreate API key creation payload. Admin-scoped API keys may optionally supply a custom API key value. ApiKeyCreateForRegion description Optional description of what the API key is used for. ApiKeyCreateForAdmin description Optional description of what the API key is used for. apiKey Optional custom API key value. Only allowed for admin-scoped API keys. API key created successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data ApiKey id Internal unique identifier for the API key. tokenPreview Short preview of the API key used for identification (non-sensitive). description Optional description of what the API key is used for. regionId Identifier of the region this API key belongs to. createdAt Timestamp when the API key was created. updatedAt Timestamp when the API key was last updated. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # POST /candidates POST /candidates createCandidates Create candidates Creates one or more candidates for the authenticated region. Supports chained creation of interactions, custom fields, attachments, sandbox mode, and asynchronous processing via the Mutation Engine. Candidates callbackUrl query Optional URL to notify upon mutation completion with the result. idempotencyKey query Client-supplied idempotency key to prevent duplicate mutations. skipCustomFieldValidation query If true, disables strict validation of custom fields against the Airtable base schema. region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. Array of candidate create payloads. A maximum of 10 candidates may be created per request. CandidateCreate Payload used to create a new candidate. CandidateBase Base candidate fields shared across create, update, and output variants. name The full name of the candidate. email The primary email address of the candidate. privacyConsent Indicates whether the candidate has given consent for data processing. phone The primary phone number of the candidate. linkedin The LinkedIn profile URL of the candidate. dateOfBirth The date of birth of the candidate. address The residential address of the candidate. city The city where the candidate resides. postalCode The postal code of the candidate's address. phase Candidate phase Phase the candidate is currently in. sectorPreferences Educational sectors the candidate is interested in. Sector Educational sector. rolePreferences Roles, jobs, or positions in education the candidate is interested in. Role Role, job, or position in education. desiredQualifications Qualifications that the candidate wants to obtain. Qualification Qualification that the candidate wants to obtain. priorEducation Prior education The highest level of education the candidate has completed. subjectPreferences Subjects that the candidate is interested in. Subject Subject that the candidate is interested in. motivation Motivation letter or explanation from the candidate. privacyConsent Must be true to proceed. curriculumVitae List of curriculum vitae attachments. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. Attachment via URL filename The name of the file including its extension. url Public URL where the file can be accessed. Attachment via base64 data filename The name of the file including its extension. data Base64-encoded file contents including the data URI prefix. otherAttachments List of other attachments provided by the candidate. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. interactions Initial interactions to create for the candidate. InteractionCreate Payload used to create an interaction. InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. customFields StaticCustomFields Custom candidate fields defined per region. \_notes **Admin-only field.** Internal notes about the candidate. This field can only be set with an admin API key. Notes are not visible to the candidate. \_archived **Admin-only field.** Indicates whether the candidate is archived. This field can only be set with an admin API key. Use the `/archive` endpoint for non-admin access. Mutation Engine accepted request for asynchronous processing SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. statusCode HTTP status code indicating that the mutation has been accepted for processing. data MutationEngineAccepted status The current status of the mutation request. mutationId Unique identifier for the mutation request. idempotencyKey Idempotency key provided by client that is associated with the mutation request. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # POST /candidates/{id}/archive POST /candidates/{id}/archive archiveCandidate Archive a candidate Archives (anonymizes) a candidate by ID. On successful archival, the candidate record is permanently unretrievable. If a `callbackUrl` is provided, a copy of the candidate record *before archival* will be sent to the callback endpoint. For region-based authentication, ownership is not pre-validated at request time. If the candidate does not belong to the region, the mutation engine will reject the operation and propagate the error to the callback URL if provided. Candidates id path Unique identifier of the candidate. callbackUrl query Optional URL to notify upon mutation completion with the result. idempotencyKey query Client-supplied idempotency key to prevent duplicate mutations. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. Mutation Engine accepted request for asynchronous processing SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. statusCode HTTP status code indicating that the mutation has been accepted for processing. data MutationEngineAccepted status The current status of the mutation request. mutationId Unique identifier for the mutation request. idempotencyKey Idempotency key provided by client that is associated with the mutation request. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data # POST /candidates/{id}/attachments POST /candidates/{id}/attachments addCandidateAttachments Add attachments to candidate Uploads one or more attachments and links them to an existing candidate record. The candidate must exist and belong to the authenticated region. Existing attachments on the candidate are preserved; new attachments are appended. At least one attachment field (`curriculumVitae` or `otherAttachments`) must be provided. Candidates Attachments id path Unique identifier of the candidate. callbackUrl query Optional URL to notify upon mutation completion with the result. idempotencyKey query Client-supplied idempotency key to prevent duplicate mutations. region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. Attachment create payload Payload used to add attachments to an existing candidate. At least one of `curriculumVitae` or `otherAttachments` must be provided and contain at least one item. curriculumVitae List of attachments representing the candidate's curriculum vitae. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. Attachment via URL filename The name of the file including its extension. url Public URL where the file can be accessed. Attachment via base64 data filename The name of the file including its extension. data Base64-encoded file contents including the data URI prefix. otherAttachments List of other attachments and documents provided by the candidate. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. Mutation Engine accepted request for asynchronous processing SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. statusCode HTTP status code indicating that the mutation has been accepted for processing. data MutationEngineAccepted status The current status of the mutation request. mutationId Unique identifier for the mutation request. idempotencyKey Idempotency key provided by client that is associated with the mutation request. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Requested resource does not exist Error statusCode statusMessage message data # POST /candidates/{id}/attachments/delete POST /candidates/{id}/attachments/delete deleteCandidateAttachments Remove attachments from a candidate Removes one or more attachments from an existing candidate record. At least one attachment must be specified, either in `curriculumVitae` or `otherAttachments`. Authorization to the candidate record is enforced before the mutation is accepted. Candidates Attachments id path Unique identifier of the candidate. callbackUrl query Optional URL to notify upon mutation completion with the result. idempotencyKey query Client-supplied idempotency key to prevent duplicate mutations. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. Attachment delete payload Payload used to remove attachments from an existing candidate. At least one of `curriculumVitae` or `otherAttachments` must be provided and contain at least one item. curriculumVitae List of curriculum vitae attachments to be removed from the candidate. id Attachment ID of the curriculum vitae file to remove. otherAttachments List of other attachments to be removed from the candidate. id Attachment ID of the file to remove. Mutation Engine accepted request for asynchronous processing SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. statusCode HTTP status code indicating that the mutation has been accepted for processing. data MutationEngineAccepted status The current status of the mutation request. mutationId Unique identifier for the mutation request. idempotencyKey Idempotency key provided by client that is associated with the mutation request. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data # POST /candidates/{id}/interactions POST /candidates/{id}/interactions createCandidateInteractions Create interactions for a candidate Creates one or more interactions for a specific candidate. A maximum of 10 interactions can be created in a single request. For region-based authentication, ownership of the candidate is not pre-validated at request time. If the candidate does not belong to the region, the mutation engine will reject the operation and propagate the error to the callback URL if provided. Candidates Interactions id path Unique identifier of the candidate. callbackUrl query Optional URL to notify upon mutation completion with the result. idempotencyKey query Client-supplied idempotency key to prevent duplicate mutations. region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. Array of interaction payloads to create. InteractionCreate Payload used to create an interaction. InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. Mutation Engine accepted request for asynchronous processing SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. statusCode HTTP status code indicating that the mutation has been accepted for processing. data MutationEngineAccepted status The current status of the mutation request. mutationId Unique identifier for the mutation request. idempotencyKey Idempotency key provided by client that is associated with the mutation request. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data # POST /candidates/upsert POST /candidates/upsert upsertCandidate Upsert a candidate Creates or updates a candidate. If a candidate with the same email (case-insensitive) or phone number (normalized) already exists, the candidate will be updated. Otherwise, a new candidate will be created. ⚠️ Custom fields are only applied when creating a new candidate, not when updating an existing one. Candidates callbackUrl query Optional URL to notify upon mutation completion with the result. idempotencyKey query Client-supplied idempotency key to prevent duplicate mutations. skipCustomFieldValidation query If true, disables strict validation of custom fields against the Airtable base schema. region query Region context override. Only applicable when using an **admin-scoped API key**. Accepts either a region ID or Airtable record ID. Ignored for non-admin API keys. x-api-sandbox header Optional sandbox flag. When set to true, the request is executed in sandbox mode. Defaults to false if omitted. CandidateCreate Payload used to create a new candidate. CandidateBase Base candidate fields shared across create, update, and output variants. name The full name of the candidate. email The primary email address of the candidate. privacyConsent Indicates whether the candidate has given consent for data processing. phone The primary phone number of the candidate. linkedin The LinkedIn profile URL of the candidate. dateOfBirth The date of birth of the candidate. address The residential address of the candidate. city The city where the candidate resides. postalCode The postal code of the candidate's address. phase Candidate phase Phase the candidate is currently in. sectorPreferences Educational sectors the candidate is interested in. Sector Educational sector. rolePreferences Roles, jobs, or positions in education the candidate is interested in. Role Role, job, or position in education. desiredQualifications Qualifications that the candidate wants to obtain. Qualification Qualification that the candidate wants to obtain. priorEducation Prior education The highest level of education the candidate has completed. subjectPreferences Subjects that the candidate is interested in. Subject Subject that the candidate is interested in. motivation Motivation letter or explanation from the candidate. privacyConsent Must be true to proceed. curriculumVitae List of curriculum vitae attachments. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. Attachment via URL filename The name of the file including its extension. url Public URL where the file can be accessed. Attachment via base64 data filename The name of the file including its extension. data Base64-encoded file contents including the data URI prefix. otherAttachments List of other attachments provided by the candidate. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. interactions Initial interactions to create for the candidate. InteractionCreate Payload used to create an interaction. InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. customFields StaticCustomFields Custom candidate fields defined per region. \_notes **Admin-only field.** Internal notes about the candidate. This field can only be set with an admin API key. Notes are not visible to the candidate. \_archived **Admin-only field.** Indicates whether the candidate is archived. This field can only be set with an admin API key. Use the `/archive` endpoint for non-admin access. Mutation Engine accepted request for asynchronous processing SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. statusCode HTTP status code indicating that the mutation has been accepted for processing. data MutationEngineAccepted status The current status of the mutation request. mutationId Unique identifier for the mutation request. idempotencyKey Idempotency key provided by client that is associated with the mutation request. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # POST /regions POST /regions createRegion Create a new region Creates a new region. **Admin-only endpoint.** Regions Admin RegionCreate RegionEditable name The name of the region administrativeEmail Contact email for administrative issues techSupportEmail Contact email for technical support. This is usually the person utilizing the API. baseId The Airtable base ID for the region recordId The Airtable record ID for the region Region created successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data Region id Internal unique identifier for the region. recordId Airtable record ID identifying the region. baseId Airtable base ID the region belongs to. name Name of the region. status Current status of the region. administrativeEmail Primary administrative contact email for the region. techSupportEmail Technical support contact email for the region. createdAt Timestamp when the region was created. updatedAt Timestamp when the region was last updated. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # POST /webhooks/regions POST /webhooks/regions upsertRegionFromWebhook Upsert region from webhook Processes an Airtable webhook event for region data changes and upserts the corresponding region record. Requires a webhook-scoped API key. Webhooks RegionWebhookPayload name The name of the region. isAirtableUser Indicates whether the Airtable user is active. Used to derive region status. recordId Airtable record ID identifying the region. baseId Airtable base ID the region belongs to. Region upserted successfully SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. data Region id Internal unique identifier for the region. recordId Airtable record ID identifying the region. baseId Airtable base ID the region belongs to. name Name of the region. status Current status of the region. administrativeEmail Primary administrative contact email for the region. techSupportEmail Technical support contact email for the region. createdAt Timestamp when the region was created. updatedAt Timestamp when the region was last updated. Bad request (may include validation issues) BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData Missing or invalid authentication credentials Error statusCode statusMessage message data Insufficient permissions to access the requested resource Error statusCode statusMessage message data Internal server error Error statusCode statusMessage message data # AirtableAttachment AirtableAttachment AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails small Thumbnail url width height large Thumbnail url width height full Thumbnail url width height # AirtableThumbnail AirtableThumbnail Thumbnail url width height # ApiKey ApiKey ApiKey id Internal unique identifier for the API key. tokenPreview Short preview of the API key used for identification (non-sensitive). description Optional description of what the API key is used for. regionId Identifier of the region this API key belongs to. createdAt Timestamp when the API key was created. updatedAt Timestamp when the API key was last updated. # ApiKeyCreate ApiKeyCreate ApiKeyCreate API key creation payload. Admin-scoped API keys may optionally supply a custom API key value. ApiKeyCreateForRegion description Optional description of what the API key is used for. ApiKeyCreateForAdmin description Optional description of what the API key is used for. apiKey Optional custom API key value. Only allowed for admin-scoped API keys. # ApiKeyCreateForAdmin ApiKeyCreateForAdmin ApiKeyCreateForAdmin description Optional description of what the API key is used for. apiKey Optional custom API key value. Only allowed for admin-scoped API keys. # ApiKeyCreateForRegion ApiKeyCreateForRegion ApiKeyCreateForRegion description Optional description of what the API key is used for. # AttachmentCreatePayload AttachmentCreatePayload Attachment create payload Payload used to add attachments to an existing candidate. At least one of `curriculumVitae` or `otherAttachments` must be provided and contain at least one item. curriculumVitae List of attachments representing the candidate's curriculum vitae. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. Attachment via URL filename The name of the file including its extension. url Public URL where the file can be accessed. Attachment via base64 data filename The name of the file including its extension. data Base64-encoded file contents including the data URI prefix. otherAttachments List of other attachments and documents provided by the candidate. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. # AttachmentDeletePayload AttachmentDeletePayload Attachment delete payload Payload used to remove attachments from an existing candidate. At least one of `curriculumVitae` or `otherAttachments` must be provided and contain at least one item. curriculumVitae List of curriculum vitae attachments to be removed from the candidate. id Attachment ID of the curriculum vitae file to remove. otherAttachments List of other attachments to be removed from the candidate. id Attachment ID of the file to remove. # AttachmentPayload AttachmentPayload AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. Attachment via URL filename The name of the file including its extension. url Public URL where the file can be accessed. Attachment via base64 data filename The name of the file including its extension. data Base64-encoded file contents including the data URI prefix. # BadRequestErrorResponse BadRequestErrorResponse BadRequestErrorResponse Error statusCode statusMessage message data statusCode data ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code GenericErrorData # BaseResponseMeta BaseResponseMeta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. # Candidate Candidate Candidate Candidate record as returned by the API. CandidateBase Base candidate fields shared across create, update, and output variants. name The full name of the candidate. email The primary email address of the candidate. privacyConsent Indicates whether the candidate has given consent for data processing. phone The primary phone number of the candidate. linkedin The LinkedIn profile URL of the candidate. dateOfBirth The date of birth of the candidate. address The residential address of the candidate. city The city where the candidate resides. postalCode The postal code of the candidate's address. phase Candidate phase Phase the candidate is currently in. sectorPreferences Educational sectors the candidate is interested in. Sector Educational sector. rolePreferences Roles, jobs, or positions in education the candidate is interested in. Role Role, job, or position in education. desiredQualifications Qualifications that the candidate wants to obtain. Qualification Qualification that the candidate wants to obtain. priorEducation Prior education The highest level of education the candidate has completed. subjectPreferences Subjects that the candidate is interested in. Subject Subject that the candidate is interested in. motivation Motivation letter or explanation from the candidate. id Unique identifier of the candidate record. curriculumVitae Curriculum vitae attachments stored in Airtable. AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails small Thumbnail url width height large Thumbnail url width height full Thumbnail url width height otherAttachments Other attachments stored in Airtable. AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails notes Internal notes about the candidate. Only visible to staff, not to the candidate. archived Indicates whether the candidate is archived. # CandidateBase CandidateBase CandidateBase Base candidate fields shared across create, update, and output variants. name The full name of the candidate. email The primary email address of the candidate. privacyConsent Indicates whether the candidate has given consent for data processing. phone The primary phone number of the candidate. linkedin The LinkedIn profile URL of the candidate. dateOfBirth The date of birth of the candidate. address The residential address of the candidate. city The city where the candidate resides. postalCode The postal code of the candidate's address. phase Candidate phase Phase the candidate is currently in. sectorPreferences Educational sectors the candidate is interested in. Sector Educational sector. rolePreferences Roles, jobs, or positions in education the candidate is interested in. Role Role, job, or position in education. desiredQualifications Qualifications that the candidate wants to obtain. Qualification Qualification that the candidate wants to obtain. priorEducation Prior education The highest level of education the candidate has completed. subjectPreferences Subjects that the candidate is interested in. Subject Subject that the candidate is interested in. motivation Motivation letter or explanation from the candidate. # CandidateCreate CandidateCreate CandidateCreate Payload used to create a new candidate. CandidateBase Base candidate fields shared across create, update, and output variants. name The full name of the candidate. email The primary email address of the candidate. privacyConsent Indicates whether the candidate has given consent for data processing. phone The primary phone number of the candidate. linkedin The LinkedIn profile URL of the candidate. dateOfBirth The date of birth of the candidate. address The residential address of the candidate. city The city where the candidate resides. postalCode The postal code of the candidate's address. phase Candidate phase Phase the candidate is currently in. sectorPreferences Educational sectors the candidate is interested in. Sector Educational sector. rolePreferences Roles, jobs, or positions in education the candidate is interested in. Role Role, job, or position in education. desiredQualifications Qualifications that the candidate wants to obtain. Qualification Qualification that the candidate wants to obtain. priorEducation Prior education The highest level of education the candidate has completed. subjectPreferences Subjects that the candidate is interested in. Subject Subject that the candidate is interested in. motivation Motivation letter or explanation from the candidate. privacyConsent Must be true to proceed. curriculumVitae List of curriculum vitae attachments. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. Attachment via URL filename The name of the file including its extension. url Public URL where the file can be accessed. Attachment via base64 data filename The name of the file including its extension. data Base64-encoded file contents including the data URI prefix. otherAttachments List of other attachments provided by the candidate. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. interactions Initial interactions to create for the candidate. InteractionCreate Payload used to create an interaction. InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. customFields StaticCustomFields Custom candidate fields defined per region. \_notes **Admin-only field.** Internal notes about the candidate. This field can only be set with an admin API key. Notes are not visible to the candidate. \_archived **Admin-only field.** Indicates whether the candidate is archived. This field can only be set with an admin API key. Use the `/archive` endpoint for non-admin access. # CandidateJsonSchema CandidateJsonSchema CandidateRequestJsonSchema A standards-compliant JSON Schema document describing the request payload for creating or updating a candidate. $schema The JSON Schema specification identifier. type Root type of the candidate request payload. properties Property definitions for the candidate payload. Each key maps to a JSON Schema describing that field. required List of required top-level fields for the selected operation. additionalProperties Whether additional properties outside the defined schema are allowed. # CandidatePhase CandidatePhase Candidate phase Phase the candidate is currently in. # CandidateUpdate CandidateUpdate CandidateUpdate Payload used to update an existing candidate. At least one field must be provided. CandidateBase Base candidate fields shared across create, update, and output variants. name The full name of the candidate. email The primary email address of the candidate. privacyConsent Indicates whether the candidate has given consent for data processing. phone The primary phone number of the candidate. linkedin The LinkedIn profile URL of the candidate. dateOfBirth The date of birth of the candidate. address The residential address of the candidate. city The city where the candidate resides. postalCode The postal code of the candidate's address. phase Candidate phase Phase the candidate is currently in. sectorPreferences Educational sectors the candidate is interested in. Sector Educational sector. rolePreferences Roles, jobs, or positions in education the candidate is interested in. Role Role, job, or position in education. desiredQualifications Qualifications that the candidate wants to obtain. Qualification Qualification that the candidate wants to obtain. priorEducation Prior education The highest level of education the candidate has completed. subjectPreferences Subjects that the candidate is interested in. Subject Subject that the candidate is interested in. motivation Motivation letter or explanation from the candidate. curriculumVitae Updated curriculum vitae attachments. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. Attachment via URL filename The name of the file including its extension. url Public URL where the file can be accessed. Attachment via base64 data filename The name of the file including its extension. data Base64-encoded file contents including the data URI prefix. otherAttachments Updated other attachments. AttachmentPayload Attachment provided by the client, either via URL or base64-encoded data. customFields StaticCustomFields Custom candidate fields defined per region. \_notes **Admin-only field.** Internal notes about the candidate. This field can only be set with an admin API key. Notes are not visible to the candidate. \_archived **Admin-only field.** Indicates whether the candidate is archived. This field can only be set with an admin API key. Use the `/archive` endpoint for non-admin access. # CandidateWithInteractions CandidateWithInteractions CandidateWithInteractions Candidate Candidate record as returned by the API. CandidateBase Base candidate fields shared across create, update, and output variants. name The full name of the candidate. email The primary email address of the candidate. privacyConsent Indicates whether the candidate has given consent for data processing. phone The primary phone number of the candidate. linkedin The LinkedIn profile URL of the candidate. dateOfBirth The date of birth of the candidate. address The residential address of the candidate. city The city where the candidate resides. postalCode The postal code of the candidate's address. phase Candidate phase Phase the candidate is currently in. sectorPreferences Educational sectors the candidate is interested in. Sector Educational sector. rolePreferences Roles, jobs, or positions in education the candidate is interested in. Role Role, job, or position in education. desiredQualifications Qualifications that the candidate wants to obtain. Qualification Qualification that the candidate wants to obtain. priorEducation Prior education The highest level of education the candidate has completed. subjectPreferences Subjects that the candidate is interested in. Subject Subject that the candidate is interested in. motivation Motivation letter or explanation from the candidate. id Unique identifier of the candidate record. curriculumVitae Curriculum vitae attachments stored in Airtable. AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails small Thumbnail url width height large Thumbnail url width height full Thumbnail url width height otherAttachments Other attachments stored in Airtable. AirtableAttachment Attachment object as returned by the Airtable API. id url filename size type thumbnails notes Internal notes about the candidate. Only visible to staff, not to the candidate. archived Indicates whether the candidate is archived. interactions Interaction records linked to the candidate. Interaction Interaction record as returned by the API. InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. id Unique identifier of the interaction record. name Human-readable name of the interaction. createdAt Datetime when the interaction record was created. # ErrorResponse ErrorResponse Error statusCode statusMessage message data # GenericErrorData GenericErrorData GenericErrorData # Interaction Interaction Interaction Interaction record as returned by the API. InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. id Unique identifier of the interaction record. name Human-readable name of the interaction. createdAt Datetime when the interaction record was created. # InteractionBase InteractionBase InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. # InteractionCreate InteractionCreate InteractionCreate Payload used to create an interaction. InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. # InteractionType InteractionType Interaction type Type of interaction associated with a candidate. # InteractionUpdate InteractionUpdate InteractionUpdate Payload used to update an interaction. InteractionBase Base interaction fields shared across create and output variants. type Type(s) of interaction with the candidate. Interaction type Type of interaction associated with a candidate. performedAt Date and time when the interaction took place. summary Short summary of the interaction. notes Additional notes or details about the interaction. duration Duration of the interaction in seconds. # MutationEngineAccepted MutationEngineAccepted MutationEngineAccepted status The current status of the mutation request. mutationId Unique identifier for the mutation request. idempotencyKey Idempotency key provided by client that is associated with the mutation request. # PaginationMeta PaginationMeta PaginationMeta pageSize Number of items requested per page. cursor Cursor for fetching the next page. Null when there are no more results. # PriorEducation PriorEducation Prior education The highest level of education the candidate has completed. # Qualification Qualification Qualification Qualification that the candidate wants to obtain. # Region Region Region id Internal unique identifier for the region. recordId Airtable record ID identifying the region. baseId Airtable base ID the region belongs to. name Name of the region. status Current status of the region. administrativeEmail Primary administrative contact email for the region. techSupportEmail Technical support contact email for the region. createdAt Timestamp when the region was created. updatedAt Timestamp when the region was last updated. # RegionCreate RegionCreate RegionCreate RegionEditable name The name of the region administrativeEmail Contact email for administrative issues techSupportEmail Contact email for technical support. This is usually the person utilizing the API. baseId The Airtable base ID for the region recordId The Airtable record ID for the region # RegionEditable RegionEditable RegionEditable name The name of the region administrativeEmail Contact email for administrative issues techSupportEmail Contact email for technical support. This is usually the person utilizing the API. # RegionUpdate RegionUpdate RegionUpdate RegionEditable name The name of the region administrativeEmail Contact email for administrative issues techSupportEmail Contact email for technical support. This is usually the person utilizing the API. # RegionWebhookPayload RegionWebhookPayload RegionWebhookPayload name The name of the region. isAirtableUser Indicates whether the Airtable user is active. Used to derive region status. recordId Airtable record ID identifying the region. baseId Airtable base ID the region belongs to. # RegionWithApiKeys RegionWithApiKeys RegionWithApiKeys Region id Internal unique identifier for the region. recordId Airtable record ID identifying the region. baseId Airtable base ID the region belongs to. name Name of the region. status Current status of the region. administrativeEmail Primary administrative contact email for the region. techSupportEmail Technical support contact email for the region. createdAt Timestamp when the region was created. updatedAt Timestamp when the region was last updated. apiKeys API keys associated with the region. ApiKey id Internal unique identifier for the API key. tokenPreview Short preview of the API key used for identification (non-sensitive). description Optional description of what the API key is used for. regionId Identifier of the region this API key belongs to. createdAt Timestamp when the API key was created. updatedAt Timestamp when the API key was last updated. # Role Role Role Role, job, or position in education. # Sector Sector Sector Educational sector. # Session Session Session id Unique identifier of the session. createdAt Timestamp when the session was created. expiresAt Timestamp when the session expires. # StaticCustomFields StaticCustomFields StaticCustomFields Custom candidate fields defined per region. # Subject Subject Subject Subject that the candidate is interested in. # SuccessResponse SuccessResponse SuccessResponse statusCode HTTP status code indicating the result of the request. message A human-readable message providing additional context about the response. data meta BaseResponseMeta sandboxEnabled Indicates whether the sandbox environment is enabled for the request. # ValidationErrorData ValidationErrorData ValidationErrorData issues Zod validation issues describing why the request is invalid. path message code # Admin Admin Admin-only endpoints for managing regions and system-level configuration. List all regions Create a new region Delete a region Update a region # Attachments Attachments Upload and remove attachments linked to candidates. Add attachments to candidate Remove attachments from a candidate # Auth Auth Authentication and identity verification endpoints for candidates and regions. Request email verification Get authenticated candidate Get authenticated region Create new API key Delete API key # Candidates Candidates Managing candidate data for your region. Get authenticated candidate List candidates Create candidates Find candidates Upsert a candidate Get candidate by ID Delete candidate Update a candidate Archive a candidate List candidate interactions Create interactions for a candidate Get interaction by ID Delete an interaction Update an interaction Add attachments to candidate Remove attachments from a candidate # Interactions Interactions Managing interaction records linked to candidates. List candidate interactions Create interactions for a candidate Get interaction by ID Delete an interaction Update an interaction # Regions Regions Region info, authentication, configuration, and API key management. List all regions Create a new region Delete a region Update a region Get authenticated region Create new API key Delete API key # Schema Schema Retrieve the dynamic candidate field schema for your region. Get candidate JSON Schema # Webhooks Webhooks Inbound webhook endpoints authenticated using webhook-scoped API keys. Upsert region from webhook