Add documents programatically

I’m working with a mental health clinic and we do assessments in different places through a consolidated app. I’d like to take the PDF documents that are generated by these assessments and add them to the patient’s record in OpenEMR. I’ve created a specific topic for these assessments and would like to place them there.

I can see how/where I would add the file to the file system via SFTP. But I’m not sure about the table structure in the documents table. Is there a better way to do this? Can I send something via API and it will handle both the file load and the data? Or can someone guide me on what the various columns mean?

Thank you.

1 Like

Hi @midder , check out the document service.

I’ll give it a try. Thank you.

I haven’t used the APIs before so this will be fun!

So I can’t get this to work. I’m on patch 4.

I’ve registered a few clients, have the appropriate things enabled in globals. But I can’t get it to like any scope that I send in. I even stripped it down to its most basic to see if I could get a different message. Nothing seems to work. I’m trying grant_type password.

{
“error”: “invalid_scope”,
“error_description”: “The requested scope is invalid, unknown, or malformed”,
“hint”: “Check the openid scope”,
“message”: “The requested scope is invalid, unknown, or malformed”
}

@midder how are you registering your API client? Are you able to paste a copy of your API requests for both registration and the document POST api command. (I’m assuming you are using the standard API as currently FHIR document post is not supported).

Typically this problems come when you are not passing in the scopes you need at the time of registration of your client. Your API calls are restricted to the same scopes or a subset of the scopes you asked for when you initially registered your client.

Also you will find that enabling the debug log options in Globals::Logging::System Error Logging Options will give you a much more detailed server logs of what is going on at the API level.

1 Like

{
“application_type”: “private”,
“redirect_uris”: [
http://localhost:3000
],
“post_logout_redirect_uris”: [
http://localhost:3000
],
“initiate_login_uri”: “https://localhost:3000”,
“client_name”: “document-api”,
“token_endpoint_auth_method”: “client_secret_post”,
“username”: “username”,
“password”: “password”,
“scope”: “openid api:oemr user/appointment.read user/appointment.write user/document.read user/document.write”
}

I get a client_id, secret, etc., back and then am able to activate it in the admin section of OEMR.

Can you post a sample of your token request since you are using the password grant? Feel free to replace anything confidential with * or some other psuedo value.

oauth2/default/token is where the POST is sent

I’m just using postman for my testing.

curl --location --request POST ‘https://www.URL.com/oauth2/default/token
–header ‘Content-Type: application/x-www-form-urlencoded’
–data-urlencode ‘client_id=clientIDHere’
–data-urlencode ‘grant_type=password’
–data-urlencode ‘user_role=users’
–data-urlencode ‘scope=openid’
–data-urlencode ‘password=password’
–data-urlencode ‘username=username’

I also enabled the debug logging. Where does that get logged to?

The apache error log for your OpenEMR instance. Usually in /var/log/apache2/error.log

In your example I don’t see the ‘api:oemr’ scope which needs to be included for you to get back your access token.

Thanks. Will check the logs. I’ve included many variations of scope. This was just the last one I tried to see if it would make a difference including just that one. When I include just api:oemr I get a complaint about that scope:

{
“error”: “invalid_scope”,
“error_description”: “The requested scope is invalid, unknown, or malformed”,
“hint”: “Check the api:oemr scope”,
“message”: “The requested scope is invalid, unknown, or malformed”
}

The debug log will log all of the request data received on the server and go through every single scope resolution of what is sent. You should be able to quickly identify what is going on looking at the logs.

Nothing jumps out at me. Does it actually try to resolve the localhost URL?

[07-Mar-2022 08:29:21 America/Denver] [2022-03-07T08:29:21.967531-07:00] OpenEMR.DEBUG: oauth2 request received {“endpoint”:"/default/token"} []
[07-Mar-2022 08:29:21 America/Denver] [2022-03-07T08:29:21.968736-07:00] OpenEMR.DEBUG: AuthorizationController->oauthAuthorizeToken() starting request [] []
[07-Mar-2022 08:29:21 America/Denver] [2022-03-07T08:29:21.969829-07:00] OpenEMR.DEBUG: AuthorizationController->getAuthorizationServer() creating server [] []
[07-Mar-2022 08:29:21 America/Denver] [2022-03-07T08:29:21.970639-07:00] OpenEMR.DEBUG: AuthorizationController->getAuthorizationServer() grantType is password [] []
[07-Mar-2022 08:29:21 America/Denver] [2022-03-07T08:29:21.971265-07:00] OpenEMR.DEBUG: AuthorizationController->getAuthorizationServer() authServer created [] []
[07-Mar-2022 08:29:21 America/Denver] [2022-03-07T08:29:21.971451-07:00] OpenEMR.DEBUG: ClientRepository->getClientEntity() client found {“client”:{“client_name”:“document-api”,“redirect_uri”:“http://localhost:3000”,“is_confidential”:“1”}} []
[07-Mar-2022 08:29:21 America/Denver] [2022-03-07T08:29:21.971686-07:00] OpenEMR.DEBUG: ScopeRepository->getScopeEntityByIdentifier() attempting to retrieve scope {“identifier”:“api:oemr”} []
[07-Mar-2022 08:29:21 America/Denver] [2022-03-07T08:29:21.971723-07:00] OpenEMR.DEBUG: ScopeRepository->getScopeEntityByIdentifier() attempting to build validation scopes [] []
[07-Mar-2022 08:29:21 America/Denver] [2022-03-07T08:29:21.971777-07:00] OpenEMR.DEBUG: ScopeRepository->buildScopeValidatorArray() {“requestScopeString”:“api:oemr”} []
[07-Mar-2022 08:29:21 America/Denver] [2022-03-07T08:29:21.971859-07:00] OpenEMR.DEBUG: ScopeRepository->getCurrentSmartScopes() setting up standard api scopes [] []
[07-Mar-2022 08:29:21 America/Denver] [2022-03-07T08:29:21.972630-07:00] OpenEMR.ERROR: ScopeRepository->getScopeEntityByIdentifier() request access to invalid scope {“scope”:“api:oemr”} []
[07-Mar-2022 08:29:21 America/Denver] [2022-03-07T08:29:21.972780-07:00] OpenEMR.DEBUG: AuthorizationController->oauthAuthorizeToken() OAuthServerException occurred {“message”:“The requested scope is invalid, unknown, or malformed”,“stack”:"#0 /home/xxxx/public_html/emr/vendor/league/oauth2-server/src/Grant/AbstractGrant.php(300): League\OAuth2\Server\Exception\OAuthServerException::invalidScope(‘api:oemr’, NULL)\n#1 /home/xxxx/public_html/emr/vendor/league/oauth2-server/src/Grant/PasswordGrant.php(53): League\OAuth2\Server\Grant\AbstractGrant->validateScopes(Array)\n#2 /home/xxxx/public_html/emr/vendor/league/oauth2-server/src/AuthorizationServer.php(198): League\OAuth2\Server\Grant\PasswordGrant->respondToAccessTokenRequest(Object(Nyholm\Psr7\ServerRequest), Object(OpenEMR\Common\Auth\OpenIDConnect\IdTokenSMARTResponse), Object(DateInterval))\n#3 /home/xxxx/public_html/emr/src/RestControllers/AuthorizationController.php(899): League\OAuth2\Server\AuthorizationServer->respondToAccessTokenRequest(Object(Nyholm\Psr7\ServerRequest), Object(Nyholm\Psr7\Response))\n#4 /home/xxxx/public_html/emr/oauth2/authorize.php(73): OpenEMR\RestControllers\AuthorizationController->oauthAuthorizeToken()\n#5 {main}"} []

My apologies as I was thinking of the ScopeRepository class in master which has a more detailed error log of what scopes are actually being validated against. I think to try and nail this down you could add the detailed logging lines in master in the following file here src/Common/Auth/OpenIDConnect/Repositories/ScopeRepository.php on lines 56-59.

with this:

if (array_key_exists($identifier, $this->validationScopes) === false && stripos($identifier, 'site:') === false) {
            $this->logger->error("ScopeRepository->getScopeEntityByIdentifier() request access to invalid scope", [
                "scope" => $identifier
                , 'validationScopes' => $this->validationScopes]);
            return null;
        }

And try your request again and it will tell you what scopes are currently being validated against when it finds the invalid scope. It almost looks like your validation array is empty.

One other thing to check (I believe its in the 6.0.0 release) is in your API Client registration inside OpenEMR, what do you see as the list of registered scopes. Is “api:oemr” listed as one of your allowed scopes for the client you registered?

Thanks I’ll give that a try and report back.

Yes it is listed. These are the ones listed on the page for this client:

  • openid
  • api:oemr
  • user/appointment.read
  • user/appointment.write
  • user/document.read
  • user/document.write

I’m hoping its not a bug in the 6.0.0 release that we fixed in master. We haven’t backported a large number of API changes / updates into the 6.0.0 release that will be coming out in the 6.1.0 release. The 6.0.0 release is running almost a year behind of master right now for the REST APIs.

[07-Mar-2022 09:15:04 America/Denver] [2022-03-07T09:15:04.676378-07:00] OpenEMR.DEBUG: oauth2 request received {“endpoint”:"/default/token"} []
[07-Mar-2022 09:15:04 America/Denver] [2022-03-07T09:15:04.678328-07:00] OpenEMR.DEBUG: AuthorizationController->oauthAuthorizeToken() starting request [] []
[07-Mar-2022 09:15:04 America/Denver] [2022-03-07T09:15:04.681753-07:00] OpenEMR.DEBUG: AuthorizationController->getAuthorizationServer() creating server [] []
[07-Mar-2022 09:15:04 America/Denver] [2022-03-07T09:15:04.682566-07:00] OpenEMR.DEBUG: AuthorizationController->getAuthorizationServer() grantType is password [] []
[07-Mar-2022 09:15:04 America/Denver] [2022-03-07T09:15:04.683155-07:00] OpenEMR.DEBUG: AuthorizationController->getAuthorizationServer() authServer created [] []
[07-Mar-2022 09:15:04 America/Denver] [2022-03-07T09:15:04.683668-07:00] OpenEMR.DEBUG: ClientRepository->getClientEntity() client found {“client”:{“client_name”:“document-api”,“redirect_uri”:“http://localhost:3000”,“is_confidential”:“1”}} []
[07-Mar-2022 09:15:04 America/Denver] [2022-03-07T09:15:04.683908-07:00] OpenEMR.DEBUG: ScopeRepository->getScopeEntityByIdentifier() attempting to retrieve scope {“identifier”:“api:oemr”} []
[07-Mar-2022 09:15:04 America/Denver] [2022-03-07T09:15:04.683944-07:00] OpenEMR.DEBUG: ScopeRepository->getScopeEntityByIdentifier() attempting to build validation scopes [] []
[07-Mar-2022 09:15:04 America/Denver] [2022-03-07T09:15:04.683974-07:00] OpenEMR.DEBUG: ScopeRepository->buildScopeValidatorArray() {“requestScopeString”:“api:oemr”} []
[07-Mar-2022 09:15:04 America/Denver] [2022-03-07T09:15:04.684062-07:00] OpenEMR.DEBUG: ScopeRepository->getCurrentSmartScopes() setting up standard api scopes [] []
[07-Mar-2022 09:15:04 America/Denver] [2022-03-07T09:15:04.684889-07:00] OpenEMR.ERROR: ScopeRepository->getScopeEntityByIdentifier() request access to invalid scope {“scope”:“api:oemr”,“validationScopes”:{“0”:“system/allergy.read”,“1”:“system/allergy.write”,“2”:“system/appointment.read”,“3”:“system/appointment.write”,“4”:“system/dental_issue.read”,“5”:“system/dental_issue.write”,“6”:“system/document.read”,“7”:“system/document.write”,“8”:“system/drug.read”,“9”:“system/encounter.read”,“10”:“system/encounter.write”,“11”:“system/facility.read”,“12”:“system/facility.write”,“13”:“system/immunization.read”,“14”:“system/insurance.read”,“15”:“system/insurance.write”,“16”:“system/insurance_company.read”,“17”:“system/insurance_company.write”,“18”:“system/insurance_type.read”,“19”:“system/list.read”,“20”:“system/medical_problem.read”,“21”:“system/medical_problem.write”,“22”:“system/medication.read”,“23”:“system/medication.write”,“24”:“system/message.write”,“25”:“system/patient.read”,“26”:“system/patient.write”,“27”:“system/practitioner.read”,“28”:“system/practitioner.write”,“29”:“system/prescription.read”,“30”:“system/procedure.read”,“31”:“system/soap_note.read”,“32”:“system/soap_note.write”,“33”:“system/surgery.read”,“34”:“system/surgery.write”,“35”:“system/vital.read”,“36”:“system/vital.write”,“37”:“user/allergy.read”,“38”:“user/allergy.write”,“39”:“user/appointment.read”,“40”:“user/appointment.write”,“41”:“user/dental_issue.read”,“42”:“user/dental_issue.write”,“43”:“user/document.read”,“44”:“user/document.write”,“45”:“user/drug.read”,“46”:“user/encounter.read”,“47”:“user/encounter.write”,“48”:“user/facility.read”,“49”:“user/facility.write”,“50”:“user/immunization.read”,“51”:“user/insurance.read”,“52”:“user/insurance.write”,“53”:“user/insurance_company.read”,“54”:“user/insurance_company.write”,“55”:“user/insurance_type.read”,“56”:“user/list.read”,“57”:“user/medical_problem.read”,“58”:“user/medical_problem.write”,“59”:“user/medication.read”,“60”:“user/medication.write”,“61”:“user/message.write”,“62”:“user/patient.read”,“63”:“user/patient.write”,“64”:“user/practitioner.read”,“65”:“user/practitioner.write”,“66”:“user/prescription.read”,“67”:“user/procedure.read”,“68”:“user/soap_note.read”,“69”:“user/soap_note.write”,“70”:“user/surgery.read”,“71”:“user/surgery.write”,“72”:“user/vital.read”,“73”:“user/vital.write”,“system/allergy.read”:{“description”:“OpenId Connect”},“system/allergy.write”:{“description”:“OpenId Connect”},“system/appointment.read”:{“description”:“OpenId Connect”},“system/appointment.write”:{“description”:“OpenId Connect”},“system/dental_issue.read”:{“description”:“OpenId Connect”},“system/dental_issue.write”:{“description”:“OpenId Connect”},“system/document.read”:{“description”:“OpenId Connect”},“system/document.write”:{“description”:“OpenId Connect”},“system/drug.read”:{“description”:“OpenId Connect”},“system/encounter.read”:{“description”:“OpenId Connect”},“system/encounter.write”:{“description”:“OpenId Connect”},“system/facility.read”:{“description”:“OpenId Connect”},“system/facility.write”:{“description”:“OpenId Connect”},“system/immunization.read”:{“description”:“OpenId Connect”},“system/insurance.read”:{“description”:“OpenId Connect”},“system/insurance.write”:{“description”:“OpenId Connect”},“system/insurance_company.read”:{“description”:“OpenId Connect”},“system/insurance_company.write”:{“description”:“OpenId Connect”},“system/insurance_type.read”:{“description”:“OpenId Connect”},“system/list.read”:{“description”:“OpenId Connect”},“system/medical_problem.read”:{“description”:“OpenId Connect”},“system/medical_problem.write”:{“description”:“OpenId Connect”},“system/medication.read”:{“description”:“OpenId Connect”},“system/medication.write”:{“description”:“OpenId Connect”},“system/message.write”:{“description”:“OpenId Connect”},“system/patient.read”:{“description”:“OpenId Connect”},“system/patient.write”:{“description”:“OpenId Connect”},“system/practitioner.read”:{“description”:“OpenId Connect”},“system/practitioner.write”:{“description”:“OpenId Connect”},“system/prescription.read”:{“description”:“OpenId Connect”},“system/procedure.read”:{“description”:“OpenId Connect”},“system/soap_note.read”:{“description”:“OpenId Connect”},“system/soap_note.write”:{“description”:“OpenId Connect”},“system/surgery.read”:{“description”:“OpenId Connect”},“system/surgery.write”:{“description”:“OpenId Connect”},“system/vital.read”:{“description”:“OpenId Connect”},“system/vital.write”:{“description”:“OpenId Connect”},“user/allergy.read”:{“description”:“OpenId Connect”},“user/allergy.write”:{“description”:“OpenId Connect”},“user/appointment.read”:{“description”:“OpenId Connect”},“user/appointment.write”:{“description”:“OpenId Connect”},“user/dental_issue.read”:{“description”:“OpenId Connect”},“user/dental_issue.write”:{“description”:“OpenId Connect”},“user/document.read”:{“description”:“OpenId Connect”},“user/document.write”:{“description”:“OpenId Connect”},“user/drug.read”:{“description”:“OpenId Connect”},“user/encounter.read”:{“description”:“OpenId Connect”},“user/encounter.write”:{“description”:“OpenId Connect”},“user/facility.read”:{“description”:“OpenId Connect”},“user/facility.write”:{“description”:“OpenId Connect”},“user/immunization.read”:{“description”:“OpenId Connect”},“user/insurance.read”:{“description”:“OpenId Connect”},“user/insurance.write”:{“description”:“OpenId Connect”},“user/insurance_company.read”:{“description”:“OpenId Connect”},“user/insurance_company.write”:{“description”:“OpenId Connect”},“user/insurance_type.read”:{“description”:“OpenId Connect”},“user/list.read”:{“description”:“OpenId Connect”},“user/medical_problem.read”:{“description”:“OpenId Connect”},“user/medical_problem.write”:{“description”:“OpenId Connect”},“user/medication.read”:{“description”:“OpenId Connect”},“user/medication.write”:{“description”:“OpenId Connect”},“user/message.write”:{“description”:“OpenId Connect”},“user/patient.read”:{“description”:“OpenId Connect”},“user/patient.write”:{“description”:“OpenId Connect”},“user/practitioner.read”:{“description”:“OpenId Connect”},“user/practitioner.write”:{“description”:“OpenId Connect”},“user/prescription.read”:{“description”:“OpenId Connect”},“user/procedure.read”:{“description”:“OpenId Connect”},“user/soap_note.read”:{“description”:“OpenId Connect”},“user/soap_note.write”:{“description”:“OpenId Connect”},“user/surgery.read”:{“description”:“OpenId Connect”},“user/surgery.write”:{“description”:“OpenId Connect”},“user/vital.read”:{“description”:“OpenId Connect”},“user/vital.write”:{“description”:“OpenId Connect”}}} []
[07-Mar-2022 09:15:04 America/Denver] [2022-03-07T09:15:04.685162-07:00] OpenEMR.DEBUG: AuthorizationController->oauthAuthorizeToken() OAuthServerException occurred {“message”:“The requested scope is invalid, unknown, or malformed”,“stack”:"#0 /home/xxxx/public_html/emr/vendor/league/oauth2-server/src/Grant/AbstractGrant.php(300): League\OAuth2\Server\Exception\OAuthServerException::invalidScope(‘api:oemr’, NULL)\n#1 /home/xxxx/public_html/emr/vendor/league/oauth2-server/src/Grant/PasswordGrant.php(53): League\OAuth2\Server\Grant\AbstractGrant->validateScopes(Array)\n#2 /home/xxxx/public_html/emr/vendor/league/oauth2-server/src/AuthorizationServer.php(198): League\OAuth2\Server\Grant\PasswordGrant->respondToAccessTokenRequest(Object(Nyholm\Psr7\ServerRequest), Object(OpenEMR\Common\Auth\OpenIDConnect\IdTokenSMARTResponse), Object(DateInterval))\n#3 /home/xxxx/public_html/emr/src/RestControllers/AuthorizationController.php(899): League\OAuth2\Server\AuthorizationServer->respondToAccessTokenRequest(Object(Nyholm\Psr7\ServerRequest), Object(Nyholm\Psr7\Response))\n#4 /home/xxxx/public_html/emr/oauth2/authorize.php(73): OpenEMR\RestControllers\AuthorizationController->oauthAuthorizeToken()\n#5 {main}"} []

ensure scopes being sent as string and check string syntax would be my guess. also others have used password grant in v6.0

I don’t see api:oemr in what it’s validating against. What builds the list of valid scopes? Where does it come from?

I’m looking at the 6.0.0 patch 4 release branch and from what I can see it looks like the ‘api:oemr’ scope is only getting added if you also request the ‘api:fhir’. I would try registering a client that asks for both the ‘api:oemr’ scope and the ‘api:fhir’ scope and then do your token request that way. Let me know if that works.

The validation array is built in the ScopeRepository in buildScopeValidatorArray().

Getting back to this finally since I’m on 6.1 patch 1 now.

Do you have a sample request for loading a document via the API?