Forum Discussion
API to access MS forms
I need a Microsoft Forms API so that I can programmatically create new forms/polls and add them to every Microsoft Teams meeting/invite or click a button during Teams meeting to send form out to all attendees. Each meeting should be able to use a same/similar forms template but generate a new form so that CEO can poll attendees and graph responses. It seems this capability should be here already. Would be nice to be able to generate the form from a forms template on the fly and provide a Microsoft Forms bot to deliver the poll to the user in an interactive format.
JTAPPS-1exactly what I'm trying to do - programmatically create a microsoft form. After that I can use Logic Apps for example, but the creation of the form is what I'm missing. Simply put I have some variables in powershell that I want to become a microsoft form.
- NunoN370Feb 10, 2024Copper Contributor
I've actually built an API to create MS Forms and read responses programmatically, by reverse engineering chrome developer tools.
It was working fine the last time I used it, although I haven't documented/fine tuned it that much.
- amitkumar4Feb 09, 2024Copper Contributor
BinaryBotany Is this really working? I am using same setup and it doesn't work
{"error": "invalid_resource","error_description": "AADSTS500011: The resource principal named api://forms.office.com/<tenat> was not found in the tenant named <####>. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant. Trace ID: 4b98a3c5-3a61-452d-82c8-3dcae3f62b00 Correlation ID: c40cdd68-0718-47b0-9227-80b7b0553c3f Timestamp: 2024-02-09 18:56:00Z","error_codes": [500011],"timestamp": "2024-02-09 18:56:00Z","trace_id": "4b98a3c5-3a61-452d-82c8-3dcae3f62b00","correlation_id": "c40cdd68-0718-47b0-9227-80b7b0553c3f","error_uri": "https://login.microsoftonline.com/error?code=500011"} - KeithW_AUJul 18, 2023Copper ContributorYep, same issue today.
Somethings broken with SP Auth, still works via the signed in user and manually browsing.
Did anyone else figure out what went wrong?
Tried full Application and Delegated permissions for "Microsoft Forms" in AAD. - BinaryBotanyDec 02, 2022Copper Contributor
Hey audriga,
I tested my old code for this and ended up getting the same 403 error. Looks like something has changed in the backend.... again.
The calls still work via the web browser once signed in to Forms, so the API still works. It must be something to do with the service principal authentication.
Sorry!
- mpheasantDec 01, 2022Copper ContributorIn my case its a server side (daemon/ confidential client) app in python, with a user id (forms_user) and password (forms_pass) for an account with admin access to the form, the code looks something like:
-----------
app = msal.ConfidentialClientApplication(
client_id,
client_credential=secret,
authority=authority
)
token = app.acquire_token_silent([scope], account=None)
if not result:
# No suitable token exists in cache. Let's get a new one from AAD.
token = app.acquire_token_by_username_password(forms_user, forms_pass, [scope]) - mpheasantDec 01, 2022Copper Contributor
if your form is protected then using oauth2 like that doesnt support supplying the password in that way and will do the redirect to show the logon form of the tenant.
There are ways to get a token with a userid/password, one is:
https://learn.microsoft.com/en-au/azure/active-directory/develop/v2-oauth-ropc
but you're probably better off with something like the "Daemon app that calls web APIs" scenario:
https://learn.microsoft.com/en-us/azure/active-directory/develop/authentication-flows-app-scenarios#scenarios-and-supported-authentication-flows - audrigaDec 01, 2022Copper Contributor
Hi!
I tried to do exactly as you explained:- I set up a basic app registration, single tenant, no redirect URI
- I got the application ID
- I created a secret
- I actually added all Forms related permissions I found
- I got the user ID
- I requested a token with: curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'client_id=<clientId>&scope=https%3A%2F%2Fforms.office.com%2F.default&client_secret=<clientSecret>&grant_type=client_credentials' 'https://login.microsoftonline.com/<tenantId>/oauth2/v2.0/token'
- I tried to request my forms with: curl -v -X GET -H 'Authorization: Bearer <token>' https://forms.office.com/formapi/api/<tenantId>/users/<userId>/forms
I am getting an HTTP 403 on the last request. Any idea?
Thanks
- OleksiihoSep 02, 2022Copper Contributor
Maybe somebody will find it useful so I'll leave it here.
Unfortunately forms API doesn't work well for S2S solutions, simply because there's no way to get Group forms, and the problem is that sooner or later every user form get transferred to group ownership.
So if you wanna to build something on server side with full support of any forms your best bet is using automation solutions, either it's Azure Logic Apps, or Power Automate, because they have native connectors to forms, and actions which can perform any http request as long as integration account does have O365 license.
Also, there's always a choice to use MSAL and perform authentication using login / password of integration account (with assigned license of course), so you'll be able obtain appropriate user token silently, but I strongly suggest not to look into this avenue
- OleksiihoAug 22, 2022Copper Contributor
When you logged in to the Forms portal, the browser makes different requests depending on form ownership type. Per my understanding "ownerId" field can contain 2 types of owners: individual users (who own / created the form initially), or Office 365 group (when user delegates form access / permissions / ownership to whole bunch of users i.e. group).
So, in browser, if the formOwnerUserId is user's oid, it uses the following URL:
"https://forms.office.com/formapi/api/<tenantId>/users/<formOwnerUserId>/forms"
It's fine and works the same in delegated scenario and S2S
But, if the formOwnerGroupId is office 365 group's oid, it uses another url segment (/groups/ instead of /users/):
"https://forms.office.com/formapi/api/<tenantId>/groups/<formOwnerGroupId>/forms"
And that's where the problem lies with S2S.. Apparently it doesn't recognize /groups/ segment and still requires /users/ one, but when used /users/ with formOwnerGroupId it just throws error because apparently it can't treat groupId as userId, meaning that S2S can't grab any data on form which is under group ownership
You can get data using delegated access though, but still..
- mpheasantAug 22, 2022Copper Contributor
I havent seen one like that so cant say.
You should be able to find the API URL by going to that form in the browser and using "Inspect" like I mention in my comment under note 2/:
- OleksiihoAug 21, 2022Copper Contributor
Thanks for your tips. Getting form data is pretty straightforward if OwnerId field is actually ObjectId of the user. But unfortunately "https://forms.office.com/formapi/api/<tenant_Id>/users/<form_owner_id>/forms" doesn't work if the form_owner_Id is a group ObjectId. I've tried to replace users segment with groups but to no avail
Some forms in my organization are under group ownership and I'm at loss how to get data about them without using delegated access
- mpheasantJun 16, 2022Copper Contributorjust a thought can you try api permission:
Responses.Read.All
forms.read.all might only allow access to the form definition rather than any responses? - BinaryBotanyJun 16, 2022Copper Contributor
This is my set up (set up from scratch to test it), hopefully it can help narrow down the problem:
- Set up a basic App registration (single tenant, no redirect URI).
- Copy the application (client) ID
- Set up a secret. Copy the secret.
- NO API PERMISSIONS REQUIRED (as of writing this). This is strange, but for some reason, it works (for now)
- log into forms through your browser, then open a new tab and go to the following: https://forms.office.com/formapi/api/userInfo
- Grab the (user) id from that response, and use it in the following: https://forms.office.com/formapi/api/{Tenant_id}/users/{User_id}/forms
- Grab a (form) id from that response and now try the following: https://forms.office.com/formapi/api/{Tenant_id}/users/{User_id}/forms
Testing in postman, authenticate using the following and copy the token:
POST to "https://login.microsoftonline.com/{TenantID}/oauth2/v2.0/token"
Body:
- grant_type = client_credentials
- client_id = {app_id copied}
- client_secret = {app_secret copied}
- scope = https://forms.office.com/.default
- (@mpheasent used this with success as well: api://forms.office.com/c9a559d2-7aab-4f13-a6ed-e7e9c52aec87/.default)
9. Replicate the responses call with postman (note the URL encoding of (''):
GET "https://forms.office.com/formapi/api/{Tenant_id}/users/{user_id}/forms%28%27{Form_id}%27%29/responses"
Headers:
- Authorization = Bearer {token}
Haven't figured out a single call to to get all forms for all users yet either. Hope this helps someone nonetheless.
- Steve_LumJun 15, 2022Copper ContributorThank you, I very much appreciate your time. I decided to crack the nut a different way - I've got my team accepting the idea that we will attach a Flow to custom approval template Forms and store the data in Sharepoint (for now, to avoid the sql server premium connector issue) and from there we can use a maintained lookup table matching template form ID to sharepoint site/list to gather the data (lookup table to contain sharepoint schema in xml) to send along to our reporting systems. Thanks again, until next time (when we change our minds - grin).
- mpheasantJun 15, 2022Copper ContributorI wasnt sure about permissions. I only have 1 form, and there are 2 places I have permissions - the app registration, plus the service account (or user account).
1/ The App Registration "API Permissions" has this (these are probably too many but we just added a bunch of options):
Forms.Read
Forms.Read.All
Forms.ReadWrite
Forms.ReadWrite.All
Responses.Read.All
Responses.ReadWrite
User.Read
User.Read.All
* I dont know which of these above are actually required..if you're lucky one of these works for you.
2/ The service account (and in development it was just a user account) I set up has "Share to Collaborate" "view & edit" permissions directly on the MS Form I'm downloading from, which was given by the form owner.
* I dont know if this is actually required. If it is you might be out of luck. Surely though there is a way to get some sort of super user access to all forms....
I can see if I can get them to remove the form collaborate permission from the service account and see if that was actually required for my case. - Steve_LumJun 15, 2022Copper Contributor
Yes, thanks. Have done that. I can query anything I want (but only my own forms) via constructing URL if I am on the web. Just can't succeed with app reg. using C# and ConfidentialClientApplicationBuilder for auth. I get a token back but client.GetAsync always returns "Unauthorized". Right now app reg has Forms.Read.All with admin consent - doesn't help. Since I am not a tenant admin I can't see what else I might need - your post mentions additional so I might see if I can expand on that - such a pain, there should be docs on this. Thanks again.
- mpheasantJun 14, 2022Copper ContributorEssentially you need to enumerate all the forms for anyone rather than just your own?
- If you can get a list of AAD users (and OID) you could query each one to see if they have any forms. MS Graph has a documented API to get users. Not sure this would be a great way to do it as probably very few people own forms. If you have a list of users in your org that have forms, you could look up their OID by hand or through MS Graph API.
- There might be a way in forms API to query all forms in a tennant but so far its undocumented.
- Maybe you can try browsing around the MS Forms interface with the console open looking at network queries with formapi in them, and see if anything looks like what you want. If there's a way to list all forms in your org through MS Forms app in the browser, then it will be using some API somewhere and you might discover it... - mpheasantJun 14, 2022Copper ContributorFind out about App Registration and OAuth to get token to access the API , see my post above where I mention : "An app registration with a bunch of permissions such as Responses.Read.All, Forms.Read.All and others (not sure exactly the minimum reqd)."
You may also need to be using a user or service account that has 'Collaborate' permissions to the specific form, I'm not 100% sure. - mpheasantJun 14, 2022Copper Contributorin my case, Owner ID is the Object ID in the AAD portal for the user who owns the form, not sure if there are other ways it can work with forms
- mpheasantJun 14, 2022Copper ContributorSee my update in original post;
Browser Inspect page, look at Network tab, find the URL with formapi in it. - Kiko1stJun 14, 2022Copper ContributorYeah there are strange architectural choices in Customer Voice. My issue for example concerns translations : they are not accessible in the Dataverse either ...
When it comes to your issue ... I am kind of lost to be sincere. no clue what are those approval workflow ...
Anyway here's a pointer to postman ... smone tried to work on that but the work is still not finished :https://www.postman.com/sakamati/workspace/customervoice/documentation/4141638-20de1ef9-5922-4a50-bf44-5737b48d8e74 - Steve_LumJun 14, 2022Copper ContributorYes, thank you. The main Approval tables are available in CDS and I can access those. However, when one creates a custom template for Approvals the additional fields are behind-the-scenes created in the Forms repository, so the data collected as part of the custom approval workflow ends up in the forms responses - not in CDS/dataverse - which is why I need to bother with Forms at all. In my opinion, a bad architectural choice by MS. Dataverse knows/stores the form template IDs associated with Approvals so it would have been better to store that data in extension tables, e.g. ApprovalTemplateQuestions, ApprovalTemplateResponses.
- Kiko1stJun 14, 2022Copper Contributor
Postman is just a tool to work with APIs
Coming back to your issue: accessing responses ... not sure that what I will say apply to forms, but for sure it applies to Customer Voice and form pro : data is available in the dataverse tables.
They are accessible via sql queries : you just need the dynamics 365 dataverse address.
There are some limitations (you cannot get translations) but it may cover most of you need - Steve_LumJun 14, 2022Copper ContributorI'm sorry, I don't know what you are asking. I know what a form owner ID is, what I was asking was, where/how was mpheasant deriving the value(s) before construction the query URL. A tenant ID is essentially a constant that can be discovered easily in the Azure Portal but form owner IDs are "user metadata" that accrue over time as new forms are created. In order to query against those forms one would have to collect them before hand.
Right now I am struggling with accessing anything I don't explicitly "own", even though I am a member of Approvals App Admin Team. Custom approval templates are actually forms and I need to be able to read their responses, which is why I am in this game. It's a specific case within the larger general case of reading form responses, which I still cannot do with C# and ConfidentialClientApplicationBuilder.