Creating a team on a brand-new Microsoft 365 group returns 404
If you call PUT /groups/{id}/team right after POST /groups, Graph often answers 404: the new group is not replicated yet. The same call succeeds a few minutes later under the same account. Retry only on 404 (Microsoft suggests up to three retries 10 seconds apart; if it still fails, wait until 15 minutes have passed since the group was created); return 403/400 at once. And if the team still isn't created, say so as a warning — a provisioning log that shows "could not create the team" with a green tick is a state that lies.
PUT /groups/{id}/team hned po POST /groups vrací 404 – Graph novou skupinu ještě replikuje; týž požadavek o pár minut později projde. Opakujte jen 404 (Microsoft radí až tři pokusy po 10 s; když ani pak, počkat, až od založení skupiny uplyne 15 minut), 403/400 vraťte hned. A když tým nevznikne, ukažte to jako upozornění – „tým se nepodařilo založit“ se zelenou fajfkou je stav, který lže.
Symptom
A site-provisioning web part creates a Microsoft 365 group and then a team on top of it:
POST https://graph.microsoft.com/v1.0/groups
{ "displayName": "Project Apollo", "mailNickname": "project-apollo", "groupTypes": ["Unified"],
"mailEnabled": true, "securityEnabled": false, "visibility": "Public" }
PUT https://graph.microsoft.com/v1.0/groups/{id}/team
{ "memberSettings": { "allowCreateUpdateChannels": true } }
The group and its SharePoint site are created, but the team isn't. The PUT was wrapped in try/catch with no retry, and the log line "The team could not be created — it can be added later" was rendered with the same green tick as the successful steps.
Cause
Measured on a test tenant (delegated, Global Administrator):
| When | PUT /groups/{id}/team |
|---|---|
| right after the group and site were ready | 404 |
| about 5 minutes later, same account, same body | succeeded, team created |
So it is not a permission problem — it is replication. Microsoft Learn says the same on the Create team page: a group created less than 15 minutes ago may make the call fail, and it suggests retrying.
Fix
function statusOf(e: unknown): number {
const s = (e && typeof e === 'object' ? e : {}) as { statusCode?: unknown; status?: unknown };
return typeof s.statusCode === 'number' ? s.statusCode : typeof s.status === 'number' ? s.status : 0;
}
/** Retries ONLY 404 (replication). Any other error is returned at once. */
async function createTeamWithRetry(create: () => Promise<void>, attempts = 8, delayMs = 15000): Promise<number> {
for (let i = 1; ; i++) {
try { await create(); return i; }
catch (e) {
if (i >= attempts || statusOf(e) !== 404) throw e;
await new Promise(r => setTimeout(r, delayMs));
}
}
}
// usage with the SPFx Graph client
await createTeamWithRetry(() => graph.api(`/groups/${groupId}/team`).put(teamSettings));
- Retrying 403 would only delay the real message ("the app has no permission to create teams").
- Keep the total wait bounded and tell the user the step may take a couple of minutes.
- If it still fails, log it as a warning, and say who does what next (for example, "the owner can create the team in Teams from the site's Microsoft 365 group") — don't promise an "add later" button you don't have.
Takeaways
- A
404from/groups/{id}/...right afterPOST /groupsusually means "not replicated yet", not "doesn't exist". - Test the hypothesis by repeating the exact call a few minutes later under the same account before you touch permissions.
- Best-effort steps need three states in the log: done, warning, failed. A swallowed exception must not look like success.
- Once failures are visible, watch for false ones: with a delegated token, the user who calls
POST /groupsbecomes an owner automatically, so adding the same person as owner again returns 400 "One or more added object references already exist". Treat that as success — see Groupmembers/$refneeds object ID (measured on the same run).