Spaces:
Sleeping
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 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 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 around lines 151–166.
When inviteAdmin is enabled and after the org is created, the component builds:
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:
fetch(`${API_BASE}/invitations`, {
method: 'POST',
headers,
body: JSON.stringify(invitationsPayload),
});
Final JSON body (frontend side)
Common fields:
email:string- Value:
adminEmailfrom the form, trimmed. - Required by the frontend: if
inviteAdminis on and this is empty, it shows: “Admin email is required when inviting an admin” and does not call the API.
- Value:
phone:string | undefined- Value: trimmed
adminPhone, or omitted (becomesundefined→ dropped byJSON.stringify) if empty.
- Value: trimmed
invited_role:"client_admin" | "contractor_admin""client_admin"iforgType === 'Telecom operator'"contractor_admin"iforgType === 'Contractor'
client_id:string | nullcreatedOrg.idiforgType === 'Telecom operator'nulliforgType === 'Contractor'
contractor_id:string | nullcreatedOrg.idiforgType === 'Contractor'nulliforgType === 'Telecom operator'
invitation_method:"whatsapp" | "email" | "both"- Default is
"whatsapp"; user can change it in the select.
- Default is
Notably not sent:
Even though the UI captures these, they are not included in the payload:
adminFirstNameadminLastName
So the backend must be okay with only email, phone?, invited_role, client_id, contractor_id, and invitation_method.
Example payloads
Telecom operator org:
{
"email": "admin@example.com",
"phone": "+254700000000",
"invited_role": "client_admin",
"client_id": "<newly-created-client-id>",
"contractor_id": null,
"invitation_method": "whatsapp"
}
Contractor org:
{
"email": "admin@example.com",
"phone": "+254700000000",
"invited_role": "contractor_admin",
"client_id": null,
"contractor_id": "<newly-created-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.