particle-academy / microsoft-teams-php
Microsoft Teams for PHP — the service descriptor, its faker, its webhook verification, one class per operation, and a fancy-flow-php executor per node. Plain HTTP on particle-academy/fancy-connector-core; no vendor SDK.
Package info
github.com/Fancy-Friends/microsoft-teams
pkg:composer/particle-academy/microsoft-teams-php
Requires
- php: ^8.4
- particle-academy/fancy-connector-core: >=0.4.0 <2.0
Requires (Dev)
- laravel/framework: ^13.0
- laravel/pint: ^1.26
- pestphp/pest: ^5.0
Suggests
- laravel/framework: ^13.0 — the Laravel integration (src/Laravel/): a service provider, config, verified webhook routes, and MicrosoftTeams::fake(). Everything outside Laravel\ works without it.
- particle-academy/fancy-flow-php: >=0.51.0 <2.0 — runs this connector's nodes on a fancy-flow-php host (src/Flow/). Everything outside Flow\ works without it.
Provides
None
Conflicts
- particle-academy/fancy-flow-php: <0.51.0 || >=2.0
Replaces
None
This package is auto-updated.
Last update: 2026-11-08 20:03:06 UTC
README
Microsoft Teams for fancy-flow — as four imported, versioned packages, one per runtime. Not vendored source: a copy cannot be upgraded, and third-party APIs change.
| Runtime | Package | Install |
|---|---|---|
| Authoring surface (every host) | @particle-academy/microsoft-teams-ui |
npm install @particle-academy/microsoft-teams-ui |
| Node | @particle-academy/microsoft-teams-js |
npm install @particle-academy/microsoft-teams-js |
| PHP 8.4+ | particle-academy/microsoft-teams-php |
composer require particle-academy/microsoft-teams-php |
| Python 3.11+ | fancy-microsoft-teams |
pip install fancy-microsoft-teams |
The ui package is the editor surface and is React on every host — a PHP or
Python project installs it and its own runtime package, and never the js one.
What it costs you
One dependency: @particle-academy/fancy-connector-core (or
particle-academy/fancy-connector-core on Composer), which the js and php
packages pull in themselves. The Python package has zero runtime
dependencies.
No Microsoft Teams SDK. Plain HTTP, deliberately: a vendor SDK is third-party code subject to the kit's full approval bar, and one per provider is hundreds of dependencies nobody is tracking.
Setting it up
Everything below is generated from provider/manifest.json, so it cannot disagree with what the packages do.
On Microsoft Teams's side
- Register an app and add the OAuth redirect URL — In the Microsoft Entra admin center, App registrations, register (or reuse) an app and add your host's OAuth redirect URL under Authentication as a Web redirect URI. Record the Application (client) ID and create a client secret. (learn.microsoft.com)
- Grant the app an application access policy naming the target user — OAuth consent alone does not authorize meeting_create, meeting_get or transcript_list -- Microsoft Graph refuses every call with "No application access policy found for this app" until a tenant administrator runs New-CsApplicationAccessPolicy (with this app's client ID) and Grant-CsApplicationAccessPolicy naming the userId these operations will act on. Changes take up to 30 minutes to reach the Graph REST API. (learn.microsoft.com)
Every step in full — who does it, what to paste, which credentials it yields, and how you know it worked — is in SETUP.md.
Credentials
A Microsoft Teams connection holds 4 values.
Two kinds of value, and mixing them up matters. A provider credential is ONE value for the whole installation — an OAuth app's client secret serves every connected account. An account credential is one per connected account. A host that stores the second where it stores the first lets one account's credentials reach another's.
| Field | Scope | Secret | Where it comes from |
|---|---|---|---|
| Application (client) ID | per installation | not secret | From the Microsoft Entra admin center -> App registrations. ONE value for the whole installation, not per connected account. |
| Client secret | per installation | secret | A client secret of the same app registration. One value for the whole installation; Entra expires them, so record the expiry. |
| Access token | per connected account | secret | Per connected Microsoft account, and it expires after about ONE HOUR. The host refreshes it with the refresh token. |
| Refresh token (optional) | per connected account | secret | Per connected Microsoft account; issued only when the consent request includes offline_access. Every refresh answers with a NEW one -- persist it. |
Authorising
Microsoft Teams uses OAuth2 (authorization_code). The package DECLARES the exchange; the HOST performs it — a consent screen needs a browser, a redirect URI and somewhere to persist the result, and all three belong to the host.
- Authorize URL — https://login.microsoftonline.com/common/oauth2/v2.0/authorize
- Token URL — https://login.microsoftonline.com/common/oauth2/v2.0/token
- Scopes —
OnlineMeetings.ReadWrite.All,OnlineMeetingTranscript.Read.All,offline_access - Access token lifetime — 3600 seconds (1 hours). A host that never refreshes works all afternoon and is broken by morning.
The refresh tokens do not rotate: the same one is reusable, so a refresh may safely be retried and may run concurrently. Stated rather than assumed, because the opposite — a provider that spends the token and revokes the grant on a replay — looks identical until it happens.
The estate
Microsoft Teams has a test estate on the same host, reached with credentials from a SEPARATE test account you register. Selecting sandbox mode uses those credentials.
Microsoft's test estate is a Microsoft 365 Developer Program sandbox: a separate E5 tenant with its own users and its own OAuth consent, on the SAME host -- the same fact microsoft-outlook already verified for Graph generally. Eligibility is limited (Visual Studio Professional/Enterprise, ISV Success / MAICPP partners, or Premier/Unified Support) and the tenant expires after 90 days of no development activity. Without one, every call reaches a real tenant.
What it can do
Actions
meeting_create — Create meeting
Create a Teams meeting on behalf of a user and get its join link.
POST /v1.0/users/{userId}/onlineMeetings · unsafe to replay — a retried durable run does it TWICE
| Input | Required | What it is |
|---|---|---|
userId |
yes | The Microsoft Entra object id of the user the meeting is created on behalf of -- the one named in the application access policy a tenant admin granted this app. |
subject |
no | Subject |
startDateTime |
yes | ISO 8601 with an offset or Z, e.g. 2026-09-22T14:00:00Z. |
endDateTime |
yes | ISO 8601 with an offset or Z. |
passcodeRequired |
no | Passcodes are auto-generated by Teams; a custom one is not supported. Leave off for no passcode. |
meeting_get — Get meeting
Read a Teams meeting's current details.
GET /v1.0/users/{userId}/onlineMeetings/{meetingId} · reads only — safe to replay
| Input | Required | What it is |
|---|---|---|
userId |
yes | The meeting organizer's Microsoft Entra object id. |
meetingId |
yes | From meeting_create's data.id. |
transcript_list — List transcripts
List a finished meeting's transcripts and their download URLs.
GET /v1.0/users/{userId}/onlineMeetings/{meetingId}/transcripts · reads only — safe to replay
| Input | Required | What it is |
|---|---|---|
userId |
yes | The meeting organizer's Microsoft Entra object id. |
meetingId |
yes | From meeting_create's data.id. |
Run it before you have credentials
Every operation ships a faker, whether or not Microsoft Teams has a sandbox. Set a
node's mode to fake and it returns the shape Microsoft Teams actually publishes — the
same field names, deterministically — so you can wire the downstream nodes before
touching an account, a key, or a network.
This repository is generated
provider/ is the source. Everything under packages/ is emitted from it and
must not be hand-edited — CI regenerates and diffs on every push, and the
next protocol sync destroys anything it finds. See AGENTS.md.
Two namespaces, which do not match on purpose
The repo is github.com/Fancy-Friends/microsoft-teams; the packages publish under
particle-academy. Nothing derives one from the other — the names come from
weaver's friends.json and nowhere else.
Licence
MIT.