As per PandaDoc documentation there are limits for the calls your application makes to PandaDoc. Official API reference states it’s 100 requests per minute per endpoint. However, this is not always true, as I have discovered there are only 50 request per minute when we query /public/v1/templates/ endpoint. On an online session I had with Scott Bilodeau he has also confirmed that some endpoints have different limits and /public/v1/templates/ endpoint has a limit of 50 requests per minute.
Unfortunately, PandaDoc has not provided us the official data on the limits for each endpoint. Hence, we decided to figure out them ourselves by running requests against PandaDoc Public API for each of the endpoints:
Entity | Endpoint | Production Limit, requests/minute | Sandbox Limit, requests/minute | Available methods |
---|---|---|---|---|
Document(s) | /public/v1/documents/ | 50 | 10 | List, Status, Details, Send, Share, Download, Download Protected, Delete |
Template(s) | /public/v1/templates/ | 50 | 10 | List, Details and Delete |
Document Folder(s) | /public/v1/documents/folders | 100 | 10 | Create, List, Rename |
Template Folder(s) | /public/v1/documents/folders | 100 | 10 | Create, List, Rename |
As can be seen from the table different endpoints have different limits. Therefore, after an application goes over the N requests per minute limit, PandaDoc would produce an error message similar to the one below:
{"type":"request_error","detail":"Request was throttled. Expected available in 13 seconds."}
Python-pandadoc library provides the following Request Limit Managers:
- NoLimitRequestLimitManager – This Limit Manager is not a real limit manager, but just a stub. However, you can use this one for testing, when no limiting is required.
- SleepRequestLimitManager – This is the most simple implementation of a limit manager, it is suitable for a single threaded application and uses simple time.sleep() function to limit the number of requests per minute. For example setting:
SleepRequestLimitManager.__limiting_delay_seconds = 6
should produce less than 10 requests per minutes, which should be enough to run your application in the sandbox mode.
- DjangoProductionMemcachedRequestLimitManager - When calling from a single thread we can limit number of requests by just adding a delay between them, as we did with SleepRequestLimitManager. However, when an application has multiple processes or threads, another technique should be implemented. Since I heavily use Django in my projects, I have implemented a Limit Manager with Memcached, it has 2 flavors: for production limits and for sandbox limits (which are much smaller).
- DjangoSandboxMemcachedRequestLimitManager – This request limit manager is inherited from DjangoProductionMemcachedRequestLimitManager, so it has the same implementation, the only difference is much smaller number of requests per minute – 10 requests, as per PandaDoc API Refference
When you have large spikes in the number of requests at certain moments of time or number of your API calls is pretty high, so that Requests Limits become a bottle neck for your application, wisely distributing applications requests over the time can greatly improve user experience! Requests that require user interaction (e.g. creating a document upon button click, or sharing this document with a user) should get much higher priority and larger portion of the quota compared to the API request that are initiated by the background services e.g. when downloading the documents in the PDF format after these documents are complete. Therefore to differentiate these requests we use for_download=True option, so request limit manager can distinguish between these calls and apply different quotas.
Here is how request limit manager is called:
with self.__request_limit_manager(max_attempts=5, retry_delay=60, for_download=True):
response = self.get(uri)
max_attempts, retry_delay and for_download are optional parameters.
Therefore, make sure your application either implements Request Limit Manager or uses the ones provided by our python-pandadoc library.
Kos Ovechko Oct 10, 2020
Next article "PandaDoc integration issues - Refreshing the list of templates" >>