You’re seeing a 422 on `POST /api/v1/invitations`, and you want to confirm whether the frontend is sending the right payload from `CreateOrganizationModal` so you can decide if the bug is backend-side. I’ll pull up that component, inspect how it builds the request body, and then describe the exact JSON shape it sends. I'll open `CreateOrganizationModal.tsx` to see exactly how it constructs the payload for the invitations endpoint, then I'll summarize the JSON shape it sends so you can compare with the backend expectations. I'll extract the invitation payload construction from [CreateOrganizationModal](cci:1://file:///d:/atomio/swiftops-frontend/src/components/dashboard/platform-admin/CreateOrganizationModal.tsx:25:0-395:1) and describe the exact JSON shape sent to `POST /api/v1/invitations`, including field names and possible values/nullability, for both Telecom operator and Contractor flows. Then you can compare that with what the backend validator expects. Here’s the exact JSON body that [CreateOrganizationModal](cci:1://file:///d:/atomio/swiftops-frontend/src/components/dashboard/platform-admin/CreateOrganizationModal.tsx:25:0-395:1) sends to `POST /api/v1/invitations`, and how it varies between a Telecom operator and a Contractor. This should let you compare directly with your backend schema to see why you’re getting a 422. ## Shape of the `POST /api/v1/invitations` request This is built in [CreateOrganizationModal.handleSubmit](cci:1://file:///d:/atomio/swiftops-frontend/src/components/dashboard/platform-admin/CreateOrganizationModal.tsx:74:2-184:4) around lines 151–166. When `inviteAdmin` is enabled and after the org is created, the component builds: ```ts const invitedRole = orgType === 'Telecom operator' ? 'client_admin' : 'contractor_admin'; const invitationsPayload: any = { email: adminEmail.trim(), phone: adminPhone.trim() || undefined, invited_role: invitedRole, client_id: orgType === 'Telecom operator' ? createdOrg.id : null, contractor_id: orgType === 'Contractor' ? createdOrg.id : null, invitation_method: invitationMethod, }; ``` Then sends: ```ts fetch(`${API_BASE}/invitations`, { method: 'POST', headers, body: JSON.stringify(invitationsPayload), }); ``` ### Final JSON body (frontend side) **Common fields:** - `email`: `string` - Value: `adminEmail` from the form, trimmed. - Required by the frontend: if `inviteAdmin` is on and this is empty, it shows: “Admin email is required when inviting an admin” and does **not** call the API. - `phone`: `string | undefined` - Value: trimmed `adminPhone`, or omitted (becomes `undefined` → dropped by `JSON.stringify`) if empty. - `invited_role`: `"client_admin" | "contractor_admin"` - `"client_admin"` if `orgType === 'Telecom operator'` - `"contractor_admin"` if `orgType === 'Contractor'` - `client_id`: `string | null` - `createdOrg.id` if `orgType === 'Telecom operator'` - `null` if `orgType === 'Contractor'` - `contractor_id`: `string | null` - `createdOrg.id` if `orgType === 'Contractor'` - `null` if `orgType === 'Telecom operator'` - `invitation_method`: `"whatsapp" | "email" | "both"` - Default is `"whatsapp"`; user can change it in the select. **Notably *not* sent:** Even though the UI captures these, they are **not included** in the payload: - `adminFirstName` - `adminLastName` So the backend must be okay with only `email`, `phone?`, `invited_role`, `client_id`, `contractor_id`, and `invitation_method`. ### Example payloads **Telecom operator org:** ```json { "email": "admin@example.com", "phone": "+254700000000", "invited_role": "client_admin", "client_id": "", "contractor_id": null, "invitation_method": "whatsapp" } ``` **Contractor org:** ```json { "email": "admin@example.com", "phone": "+254700000000", "invited_role": "contractor_admin", "client_id": null, "contractor_id": "", "invitation_method": "whatsapp" } ``` If `phone` is left blank, that key is omitted completely. --- If you paste your backend `InvitationCreate`/Pydantic schema (or similar) I can line‑by‑line compare and point out the exact mismatch likely causing the 422.