Package: cloudflare
cloudflare-pythonInstallationTwo methods are provided to install this software.
Use PyPi (seepackagedetails) or GitHub (seepackagedetails).Via PyPI$sudopipinstallcloudflare$Yes - that simple! (the sudo may not be needed in some cases).Via github$gitclonehttps://github.com/cloudflare/python-cloudflare$cdpython-cloudflare$./setup.pybuild$sudo./setup.pyinstall$Or whatever variance of that you want to use.
There is a Makefile included.Cloudflare name change - dropping the capital FIn Sepember/October 2016 the company modified its company name and dropped the capital F.
However, for now (and for backward compatibility reasons) the class name stays the same.Cloudflare API version 4The Cloudflare API can be foundhere.
Each API call is provided via a similarly named function within theCloudFlareclass.
A full list is provided below.Example codeAll example code is available on GitHub (seepackagein theexamplesfolder).BlogThis package was initially introducedherevia Cloudflare'sblog.Getting StartedA very simple listing of zones within your account; including the IPv6 status of the zone.importCloudFlaredefmain():cf=CloudFlare.CloudFlare()zones=cf.zones.get()forzoneinzones:zone_id=zone['id']zone_name=zone['name']print("zone_id=%szone_name=%s"%(zone_id,zone_name))if__name__=='__main__':main()This example works when there are less than 50 zones (50 is the default number of values returned from a query like this).Now lets expand on that and add code to show the IPv6 and SSL status of the zones. Lets also query 100 zones.importCloudFlaredefmain():cf=CloudFlare.CloudFlare()zones=cf.zones.get(params={'per_page':100})forzoneinzones:zone_id=zone['id']zone_name=zone['name']settings_ssl=cf.zones.settings.ssl.get(zone_id)ssl_status=settings_ssl['value']settings_ipv6=cf.zones.settings.ipv6.get(zone_id)ipv6_status=settings_ipv6['value']print("zone_id=%szone_name=%s"%(zone_id,zone_name))print("ssl_status=%sipv6_status=%s"%(ssl_status,ipv6_status))if__name__=='__main__':main()In order to query more than a single page of zones, we would have to use the raw mode (described more below).
We can loop over many get calls and pass the page parameter to facilitate the paging.Raw mode is only needed when a get request has the possibility of returning many items.importCloudFlaredefmain():cf=CloudFlare.CloudFlare(raw=True)page_number=0whileTrue:page_number+=1raw_results=cf.zones.get(params={'per_page':5,'page':page_number})zones=raw_results['result']forzoneinzones:zone_id=zone['id']zone_name=zone['name']print("zone_id=%szone_name=%s"%(zone_id,zone_name))total_pages=raw_results['result_info']['total_pages']ifpage_number==total_pages:breakif__name__=='__main__':main()A more complex example follows.importCloudFlaredefmain():zone_name='example.com'cf=CloudFlare.CloudFlare()# query for the zone name and expect only one value backtry:zones=cf.zones.get(params={'name':zone_name,'per_page':1})exceptCloudFlare.exceptions.CloudFlareAPIErrorase:exit('/zones.get%d%s- api call failed'%(e,e))exceptExceptionase:exit('/zones.get -%s- api call failed'%(e))iflen(zones)==0:exit('No zones found')# extract the zone_id which is needed to process that zonezone=zones[0]zone_id=zone['id']# request the DNS records from that zonetry:dns_records=cf.zones.dns_records.get(zone_id)exceptCloudFlare.exceptions.CloudFlareAPIErrorase:exit('/zones/dns_records.get%d%s- api call failed'%(e,e))# print the results - first the zone nameprint("zone_id=%szone_name=%s"%(zone_id,zone_name))# then all the DNS records for that zonefordns_recordindns_records:r_name=dns_record['name']r_type=dns_record['type']r_value=dns_record['content']r_id=dns_record['id']print('\t',r_id,r_name,r_type,r_value)exit(0)if__name__=='__main__':main()Providing Cloudflare Username and API KeyWhen you create aCloudFlareclass you can pass some combination of these four core parameters.email- The account email (only if an API Key is being used)api- The API Key (if coding prior to Issue-114 being merged)token- The API Token (if coding after to Issue-114)certtoken- Optional Origin-CA Certificate TokenThis parameter controls how the data is returned from a successful call (see notes below).raw- An optional Raw flag (True/False) - defaults to FalseTimeouts (10s) and Retries (5) are configured by default. Should you wish to override them, use these settings:global_request_timeout- How long before each API call to Cloudflare should time out (in seconds)max_requests_retries- How many times to retry an API call when DNS lookups, socket connections, or connect timeouts occur.NOTE:max_request_retriesis only available whenuse_sessionsis not disabled.The following paramaters are for debug and/or development usagedebug- An optional Debug flag (True/False) - defaults to Falseuse_sessions- An optional Use-Sessions flag (True/False) - defaults to Trueprofile- An optional Profile name (the default isCloudflare)base_url- An optional Base URL (only used for development)email=None, key=None, token=None, certtoken=None, debug=False, raw=False, use_sessions=True, profile=None, base_url=None):Issue-114AfterIssue-114was coded and merged, the use oftokenandkeychanged; however, is backward compatible (amazingly!).If you are using only the API Token, then don't include the API Email. If you are coding prior to Issue-114, then the API Key can also be used as an API Token if the API Email is not used.Python code to create classimportCloudFlare# A minimal call - reading values from environment variables or configuration filecf=CloudFlare.CloudFlare()# A minimal call with debug enabledcf=CloudFlare.CloudFlare(debug=True)# An authenticated call using an API Token (note the missing email)cf=CloudFlare.CloudFlare(token='00000000000000000000000000000000')# An authenticated call using an API Email and API Keycf=CloudFlare.CloudFlare(email='user@example.com',key='00000000000000000000000000000000')# An authenticated call using an API Token and CA-Origin infocf=CloudFlare.CloudFlare(token='00000000000000000000000000000000',certtoken='v1.0-...')# An authenticated call using an API Email, API Key, and CA-Origin infocf=CloudFlare.CloudFlare(email='user@example.com',key='00000000000000000000000000000000',certtoken='v1.0-...')# An authenticated call using using a stored profile (see below)cf=CloudFlare.CloudFlare(profile="CompanyX"))If the account email and API key are not passed when you create the class, then they are retrieved from either the users exported shell environment variables or the .cloudflare.cfg or ~/.cloudflare.cfg or ~/.cloudflare/cloudflare.cfg files, in that order.If you're using an API Token, anycloudflare.cfgfile must either not contain anemailandkeyattribute (or they can be zero length strings) and theCLOUDFLARE_EMAILCLOUDFLARE_API_KEYenvironment variable must be unset (or zero length strings), otherwise the token (CLOUDFLARE_API_TOKENortokenattribute) will not be used.There is one call that presently doesn't need any email or token certification (the/ipscall); hence you can test without any values saved away.Using shell environment variablesNote (for latest version of code):CLOUDFLARE_EMAILhas replacedCF_API_EMAIL.CLOUDFLARE_API_KEYhas replacedCF_API_KEY.CLOUDFLARE_API_TOKENhas replacedCF_API_TOKEN.CLOUDFLARE_API_CERTKEYhas replacedCF_API_CERTKEY.Additionally, these two variables are available for testing purposes:CLOUDFLARE_API_EXTRAShas replacedCF_API_EXTRAS.CLOUDFLARE_API_URLhas replacedCF_API_URL.The older environment variable names can still be used.$exportCLOUDFLARE_EMAIL='user@example.com'$exportCLOUDFLARE_API_KEY='00000000000000000000000000000000'$exportCLOUDFLARE_API_CERTKEY='v1.0-...'$Or if using API Token.$exportCLOUDFLARE_API_TOKEN='00000000000000000000000000000000'$exportCLOUDFLARE_API_CERTKEY='v1.0-...'$These are optional environment variables; however, they do override the values set within a configuration file.Using configuration file to store email and keysThe default profile name isCloudflarefor obvious reasons.$cat~/.cloudflare/cloudflare.cfg[Cloudflare]email=user@example.com# Do not set if using an API Tokenkey=00000000000000000000000000000000certtoken=v1.0-...extras=$More than one profile can be stored within that file.
Here's an example for a work and home setup (in this example work has an API Token and home uses email/key).$cat~/.cloudflare/cloudflare.cfg[Work]token=00000000000000000000000000000000[Home]email=home@example.comkey=00000000000000000000000000000000$To select a profile, use the--profile profile-nameoption forcli4command or useprofile="profile-name"in the library call.$cli4--profileWork/zones|jq'.[]|.name'|wc-l13$

$cli4--profileHome/zones|jq'.[]|.name'|wc-l1$Here is the same in code.#!/usr/bin/env pythonimportCloudFlaredefmain():cf=CloudFlare.CloudFlare(profile="Work")...Passing your own HTTP headers to API callsThere are very specific case where a user of the library needs to add custom headers to all HTTP calls.
This is rarly needed.The addition headers can be passed via the confuration file as follows:$cat~/.cloudflare/cloudflare.cfg
...http_headers=X-Header1:valueX-Header2:value1value2value3X-Header3:"this is life as we know it"X-Header4:'two single quotes'X-Header5:
...
$Each line should have a header noun, a colon, and a verb.You can also pass these via Python calls.importCloudFlarehttp_headers=['X-Header1:value','X-Header2: value1 value2 value3','X-Header3: "this is life as we know it"','X-Header4:\'two single quotes\'','X-Header5:',]cf=CloudFlare.CloudFlare(http_headers=http_headers)...Advanced use of configuration file for authentication based on methodThe configuration file can have values that are both generic and specific to the method.
Here's an example where a project has a different API Token for reading and writing values.$cat~/.cloudflare/cloudflare.cfg[Work]token=0000000000000000000000000000000000000000token.get=0123456789012345678901234567890123456789$When a GET call is processed then the second token is used. For all other calls the first token is used.
Here's a more explict verion of that config:$cat~/.cloudflare/cloudflare.cfg[Work]token.delete=0000000000000000000000000000000000000000token.get=0123456789012345678901234567890123456789token.patch=0000000000000000000000000000000000000000token.post=0000000000000000000000000000000000000000token.put=0000000000000000000000000000000000000000$This can be used with email values also.About /certificates and certtokenTheCLOUDFLARE_API_CERTKEYorcerttokenvalues are used for the Origin-CA/certificatesAPI calls.
You can leavecerttokenin the configuration with a blank value (or omit the option variable fully).Theextrasvalues are used when adding API calls outside of the core codebase.
Technically, this is only useful for internal testing within Cloudflare.
You can leaveextrasin the configuration with a blank value (or omit the option variable fully).Exceptions and return valuesResponse dataThe response is build from the JSON in the API call.
It contains theresultsvalues; but does not contain the paging values.You can return all the paging values by calling the class with raw=True. Here's an example without paging.#!/usr/bin/env pythonimportjsonimportCloudFlaredefmain():cf=CloudFlare.CloudFlare()zones=cf.zones.get(params={'per_page':5})print("len=%d"%(zones.length()))if__name__=='__main__':main()The results are as follows.5When you add the raw option; the APIs full structure is returned. This means the paging values can be seen.#!/usr/bin/env pythonimportjsonimportCloudFlaredefmain():cf=CloudFlare.CloudFlare(raw=True)zones=cf.zones.get(params={'per_page':5})print("len=%d"%(zones.length()))print(json.dumps(zones,indent=4,sort_keys=True))if__name__=='__main__':main()This produces.5
{
    "result": [
        ...
    ],
    "result_info": {
        "count": 5,
        "page": 1,
        "per_page": 5,
        "total_count": 31,
        "total_pages": 7
    }
}A full example of paging is provided below.ExceptionsThe library will raiseCloudFlareAPIErrorwhen the API call fails.
The exception returns both an integer and textual message in one value.importCloudFlare...tryr=...exceptCloudFlare.exceptions.CloudFlareAPIErrorase:exit('api error:%d%s'%(e,e))...The other raised response isCloudFlareInternalErrorwhich can happen when calling an invalid method.In some cases more than one error is returned. In this case the return valueeis also an array.
You can iterate over that array to see the additional error.importsysimportCloudFlare...tryr=...exceptCloudFlare.exceptions.CloudFlareAPIErrorase:iflen(e)>0:sys.stderr.write('api error - more than one error value returned!\n')forxine:sys.stderr.write('api error:%d%s\n'%(x,x))exit('api error:%d%s'%(e,e))...Exception handlingHere's code using the CLI commandcli4of the responses passed back in exceptions.First a simple get with a clean (non-error) response.$cli4/zones/:example.com/dns_records|jq-c'.[]|{"name":.name,"type":.type,"content":.content}'{"name":"example.com","type":"MX","content":"something.example.com"}{"name":"something.example.com","type":"A","content":"10.10.10.10"}$Next a simple/single error response.
This is simulated by providing incorrect authentication information.$CLOUDFLARE_EMAIL='someone@example.com'cli4/zones/
cli4:/zones-9103UnknownX-Auth-KeyorX-Auth-Email
$More than one call can be done on the same command line. In this mode, the connection is preserved between calls.$cli4/user/organizations/user/invites
...
$Note that the output is presently two JSON structures one after the other - so less useful that you may think.Finally, a command that provides more than one error response.
This is simulated by passing an invalid IPv4 address to a DNS record creation.$cli4--postname='foo'type=Acontent="NOT-A-VALID-IP-ADDRESS"/zones/:example.com/dns_records
cli4:/zones/:example.com/dns_records-9005ContentforArecordisinvalid.MustbeavalidIPv4address
cli4:/zones/:example.com/dns_records-1004DNSValidationError
$Included example codeTheexamplesfolder contains many examples in both simple and verbose formats.You can see the installed path of these files directly viacli4 -e(orcli4 --examples) command.$cli4-e
Python.pyfiles:.../opt/homebrew/lib/python3.11/site-packages/examples/example_always_use_https.py...
Bash.shfiles:.../opt/homebrew/lib/python3.11/site-packages/examples/example_paging_thru_zones.sh...
$The exact path will vary depending on your system.
The above example is MacOS and Python 3.9 hence the/opt/homebrew/lib/python3.11/site-packages/path.
One Linux, the Python pip command may install the code is a system location like/usr/lib/python3/dist-packagesor~/.local/lib/python3.9/site-packages/or something different.
Thecli4 -ecommand will try to decode the location and display the example files.If you are running release before Python 3.9 then you will be asked to install the following:$pipinstallimportlib_resources
...
$It will show up if you are running on an older system. For example, this is the results from running on Win7:U:\Users\Bobby>cli4-e
Module"importlib_resources"missing-please"pip install importlib_resources"asyourPythonversionislowerthan3.9

U:\Users\Bobby>python-V
Python3.8.3

U:\Users\Bobby>Upgrading from an older version of Python is always recommended. Upgrading from Win7 is by-default even more important!A DNS zone code example#!/usr/bin/env pythonimportsysimportCloudFlaredefmain():zone_name=sys.argv[1]cf=CloudFlare.CloudFlare()zone_info=cf.zones.post(data={'jump_start':False,'name':zone_name})zone_id=zone_info['id']dns_records=[{'name':'foo','type':'AAAA','content':'2001:d8b::1'},{'name':'foo','type':'A','content':'192.168.0.1'},{'name':'duh','type':'A','content':'10.0.0.1','ttl':120},{'name':'bar','type':'CNAME','content':'foo'},{'name':'shakespeare','type':'TXT','content':"What's in a name? That which we call a rose by any other name ..."}]fordns_recordindns_records:r=cf.zones.dns_records.post(zone_id,data=dns_record)exit(0)if__name__=='__main__':main()A DNS zone delete code example (be careful)#!/usr/bin/env pythonimportsysimportCloudFlaredefmain():zone_name=sys.argv[1]cf=CloudFlare.CloudFlare()zone_info=cf.zones.get(params={'name':zone_name})zone_id=zone_info['id']dns_name=sys.argv[2]dns_records=cf.zones.dns_records.get(zone_id,params={'name':dns_name+'.'+zone_name})fordns_recordindns_records:dns_record_id=dns_record['id']r=cf.zones.dns_records.delete(zone_id,dns_record_id)exit(0)if__name__=='__main__':main()CLIAll API calls can be called from the command line via thecli4command.
Additionally, thecli4command will convert domain name or account name prefixed with a colon (:) into the correct identifier.
e.g. to viewexample.comyou can usecli4 /zones/:example.com.
You can pass the zone identifier (or account identifier or any identifier) with a colon followed by the identifier as a hex number 32 characters long.$cli4[-V|--version][-h|--help][-v|--verbose]\[-e|--examples]\[-q|--quiet]\[-j|--json][-y|--yaml][-n|--ndjson][-i|--image]\[-r|--raw]\[-d|--dump]\[-A|--openapiurl]\[-b|--binary]\[-p|--profileprofile-name]\[--get|--patch|--post|--put|--delete]\[item=value|item=@filename|@filename...]/command...CLI parameters for POST/PUT/PATCHFor API calls that need to pass data or parameters there is various formats to use.The simplest form isitem=value. This passes the value as a string within the APIs JSON data.If you need a numeric value passed then==can be used to force the value to be treated as a numeric value within the APIs JSON data.
For example:item==value.if you need to pass a list of items; then[]can be used. For example:pool_id1="11111111111111111111111111111111"pool_id2="22222222222222222222222222222222"pool_id3="33333333333333333333333333333333"cli4--postglobal_pools="[${pool_id1},${pool_id2},${pool_id3}]"region_pools="[ ]"/user/load_balancers/mapsData or parameters can be either named or unnamed.
It can not be both.
Named is the majority format; as described above.
Unnamed parameters simply don't have anything before the=sign, as in=value.
This format is presently only used by the Cloudflare Load Balancer API calls.
For example:cli4--put="00000000000000000000000000000000"/user/load_balancers/maps/:00000000000000000000000000000000/region/:WNAMData can also be uploaded from file contents. Using theitem=@filenameformat will open the file and the contents uploaded in the POST.CLI outputThe default output from the CLI command is in JSON.
It can also output YAML format (i.e. human readable).
This is controled by the--yamlor--jsonflags (JSON is the default).
There is also a--ndjsonflag for use with line based JSON data - this is mainly used for log data.Additonally the output can be plain text or binary image format depending on the results from the API call (some calls results in non JSON results).
The--imageflag will return the data in the same format as the API's results.Simple CLI callscli4 /user/billing/profilecli4 /user/invitescli4 /zones/:example.comcli4 /zones/:example.com/dnsseccli4 /zones/:example.com/settings/ipv6cli4 --put /zones/:example.com/activation_checkcli4 /zones/:example.com/keyless_certificatescli4 /zones/:example.com/analytics/dashboardMore complex CLI callsHere is the creation of a DNS entry, followed by a listing of that entry and then the deletion of that entry.$$cli4--postname="test"type="A"content="10.0.0.1"/zones/:example.com/dns_records{"id":"00000000000000000000000000000000","name":"test.example.com","type":"A","content":"10.0.0.1",...}$

$cli4/zones/:example.com/dns_records/:test.example.com|jq'{"id":.id,"name":.name,"type":.type,"content":.content}'{"id":"00000000000000000000000000000000","name":"test.example.com","type":"A","content":"10.0.0.1"}$cli4--delete/zones/:example.com/dns_records/:test.example.com|jq-c.{"id":"00000000000000000000000000000000"}$There's the ability to handle dns entries with multiple values.
This produces more than one API call within the command.$cli4/zones/:example.com/dns_records/:test.example.com|jq-c'.[]|{"id":.id,"name":.name,"type":.type,"content":.content}'{"id":"00000000000000000000000000000000","name":"test.example.com","type":"A","content":"192.168.0.1"}{"id":"00000000000000000000000000000000","name":"test.example.com","type":"AAAA","content":"2001:d8b::1"}$Here are the cache purging commands.$cli4--deletepurge_everything=true/zones/:example.com/purge_cache|jq-c.{"id":"00000000000000000000000000000000"}$

$cli4--deletefiles='[http://example.com/css/styles.css]'/zones/:example.com/purge_cache|jq-c.{"id":"00000000000000000000000000000000"}$

$cli4--deletefiles='[http://example.com/css/styles.css,http://example.com/js/script.js]'/zones/:example.com/purge_cache|jq-c.{"id":"00000000000000000000000000000000"}$

$cli4--deletetags='[tag1,tag2,tag3]'/zones/:example.com/purge_cache|jq-c.
cli4:/zones/:example.com/purge_cache-1107Onlyenterprisezonescanpurgebytag.
$A somewhat useful listing of available plans for a specific zone.$cli4/zones/:example.com/available_plans|jq-c'.[]|{"id":.id,"name":.name}'{"id":"00000000000000000000000000000000","name":"Pro Website"}{"id":"00000000000000000000000000000000","name":"Business Website"}{"id":"00000000000000000000000000000000","name":"Enterprise Website"}{"id":"0feeeeeeeeeeeeeeeeeeeeeeeeeeeeee","name":"Free Website"}$Cloudflare CA CLI callsHere's some Cloudflare CA calls. Note the need of thezone_id=parameter with the basic/certificatescall.$cli4/zones/:example.com|jq-c'.|{"id":.id,"name":.name}'{"id":"12345678901234567890123456789012","name":"example.com"}$

$cli4zone_id=12345678901234567890123456789012/certificates|jq-c'.[]|{"id":.id,"expires_on":.expires_on,"hostnames":.hostnames,"certificate":.certificate}'{"id":"123456789012345678901234567890123456789012345678","expires_on":"2032-01-29 22:36:00 +0000 UTC","hostnames":["*.example.com","example.com"],"certificate":"-----BEGIN CERTIFICATE-----\n ... "}{"id":"123456789012345678901234567890123456789012345678","expires_on":"2032-01-28 23:23:00 +0000 UTC","hostnames":["*.example.com","example.com"],"certificate":"-----BEGIN CERTIFICATE-----\n ... "}{"id":"123456789012345678901234567890123456789012345678","expires_on":"2032-01-28 23:20:00 +0000 UTC","hostnames":["*.example.com","example.com"],"certificate":"-----BEGIN CERTIFICATE-----\n ... "}$A certificate can be viewed via a simple GET request.$cli4/certificates/:123456789012345678901234567890123456789012345678{"certificate":"-----BEGIN CERTIFICATE-----\n ... ","expires_on":"2032-01-29 22:36:00 +0000 UTC","hostnames":["*.example.com","example.com"],"id":"123456789012345678901234567890123456789012345678","request_type":"origin-rsa"}$Creating a certificate. This is done with aPOSTrequest. Note the use of==in order to pass a decimal number (vs. string) in JSON. The CSR is not shown for simplicity sake.$CSR=`catexample.com.csr`$cli4--posthostnames='["example.com","*.example.com"]'requested_validity==365request_type="origin-ecc"csr="$CSR"/certificates{"certificate":"-----BEGIN CERTIFICATE-----\n ... ","csr":"-----BEGIN CERTIFICATE REQUEST-----\n ... ","expires_on":"2018-09-27 21:47:00 +0000 UTC","hostnames":["*.example.com","example.com"],"id":"123456789012345678901234567890123456789012345678","request_type":"origin-ecc","requested_validity":365}$Deleting a certificate can be done with aDELETEcall.$cli4--delete/certificates/:123456789012345678901234567890123456789012345678{"id":"123456789012345678901234567890123456789012345678","revoked_at":"0000-00-00T00:00:00Z"}$Paging CLI callsThe--rawcommand provides access to the paging returned values.
See the API documentation for all the info.
Here's an example of how to page thru a list of zones (it's included in the examples folder asexample_paging_thru_zones.sh).
Note the use of==to pass a number vs a string as paramater.:tmp=/tmp/$$_trap"rm${tmp}; exit 0"01215PAGE=0whiletruedocli4--rawper_page==5page==${PAGE}/zones>${tmp}domains=`jq-c'.|.result|.[]|.name'<${tmp}|tr-d'"'`result_info=`jq-c'.|.result_info'<${tmp}`COUNT=`echo"${result_info}"|jq.count`PAGE=`echo"${result_info}"|jq.page`PER_PAGE=`echo"${result_info}"|jq.per_page`TOTAL_COUNT=`echo"${result_info}"|jq.total_count`TOTAL_PAGES=`echo"${result_info}"|jq.total_pages`echoCOUNT=${COUNT}PAGE=${PAGE}PER_PAGE=${PER_PAGE}TOTAL_COUNT=${TOTAL_COUNT}TOTAL_PAGES=${TOTAL_PAGES}--${domains}if["${PAGE}"=="${TOTAL_PAGES}"]then## last sectionbreakfi# grab the next pagePAGE=`expr${PAGE}+1`doneIt produces the following results.COUNT=5PAGE=1PER_PAGE=5TOTAL_COUNT=31TOTAL_PAGES=7--accumsan.exampleauctor.exampleconsectetur.exampledapibus.exampleelementum.exampleCOUNT=5PAGE=2PER_PAGE=5TOTAL_COUNT=31TOTAL_PAGES=7--felis.exampleiaculis.exampleipsum.examplejusto.examplelacus.exampleCOUNT=5PAGE=3PER_PAGE=5TOTAL_COUNT=31TOTAL_PAGES=7--lectus.examplelobortis.examplemaximus.examplemorbi.examplepharetra.exampleCOUNT=5PAGE=4PER_PAGE=5TOTAL_COUNT=31TOTAL_PAGES=7--porttitor.examplepotenti.examplepretium.examplepurus.examplequisque.exampleCOUNT=5PAGE=5PER_PAGE=5TOTAL_COUNT=31TOTAL_PAGES=7--sagittis.examplesemper.examplesollicitudin.examplesuspendisse.exampletortor.exampleCOUNT=1PAGE=7PER_PAGE=5TOTAL_COUNT=31TOTAL_PAGES=7--varius.examplevehicula.examplevelit.examplevelit.examplevitae.exampleCOUNT=5PAGE=6PER_PAGE=5TOTAL_COUNT=31TOTAL_PAGES=7--vivamus.examplePaging thru lists (using cursors)Some API calls use cursors to read beyond the initally returned values. See the API page in order to see which API calls do this.$ACCOUNT_ID="00000000000000000000000000000000"$LIST_ID="00000000000000000000000000000000"$
$cli4--raw/accounts/::${ACCOUNT_ID}/rules/lists/::${LIST_ID}/items>/tmp/page1.json
$after=`jq-r'.result_info.cursors.after'</tmp/page1.json`$echo"after=$after"after=Mxm4GVmKjYbFjy2VxMPipnJigm1M_s6lCS9ABR9wx-RM2A
$Once we have theaftervalue, we can pass it along in order to read the next hunk of values. We finish whenafterreturns as null (or isn't present).$cli4--rawcursor="$after"/accounts/::${ACCOUNT_ID}/rules/lists/::${LIST_ID}/items>/tmp/page2.json
$after=`jq-r'.result_info.cursors.after'</tmp/page2.json`$echo"after=$after"after=null
$We can see the results now in two files.$jq-c'.result[]'</tmp/page1.json|wc-l25$

$jq-c'.result[]'</tmp/page2.json|wc-l5$

$forfin/tmp/page?.json;dojq-r'.result[]|.id,.ip,.comment'<$f|paste---;done|column-s'   '-t
0fe44928258549feb47126a966fbf4a00.0.0.0allzero
2e1e02120f5e466f8c0e26375e4cf4c81.0.0.1CloudflareDNSa
9ca5fd0ac6f54fdbb9dedd3fb72ce2da1.1.1.1CloudflareDNSb
b3654987446743738c782f36ebe074f510.0.0.0/8RFC1918space
90bec8ce37d242faa2e27d1e78c1d8e2103.21.244.0/22CloudflareIP
970a3c810cda41af9bef2c36a1892f7e103.22.200.0/22CloudflareIP
3ec8516158bf4f3cac18210f611ee541103.31.4.0/22CloudflareIP
ee9d268367204e6bb8e5e4c907f22de8104.16.0.0/12CloudflareIP
93ae02eda9774c45840af367a02fe529108.162.192.0/18CloudflareIP
62891ebf6db44aa494d79a6401af185e131.0.72.0/22CloudflareIP
cac40cd940cc470582b8c912a8a12bea141.101.64.0/18CloudflareIP
f6d5eacd81a2407f8e0d81caee21e7f8162.158.0.0/15CloudflareIP
3d538dfc38ab471d9d3fe78332acfa4e172.16.0.0/12RFC1918space
f353cb8f98424837ad35382a22b9debe172.64.0.0/13CloudflareIP
78f3e1a0bafc41f88d4d40ad49a642e0173.245.48.0/20CloudflareIP
c23a545475c54c32a7681c6b508d3e80188.114.96.0/20CloudflareIP
f693237c9e294fe481221cbc2d7c20ef190.93.240.0/20CloudflareIP
6d465ab3a0994c07827ebdcf8f34d977192.168.0.0/16RFC1918space
1ad1e634b3664bac939086185c62faf7197.234.240.0/22CloudflareIP
5d2968e7b3114d8e869a379d71c8ba86198.41.128.0/17CloudflareIP
6a69de60b31448fa864f0a3ac5abe8d0224.0.0.0/24Multicast
30749cce89af4ab3a80e308294f46a46240.0.0.0/4ClassE
2b32c67ea4d044628abe39f28662d8f0255.255.255.255allones
cc7cd828b2fb4bcfb9391c2d3ef8d0682400:cb00::/32CloudflareIP
b30d4cbd7dcd48729e8ebeda552e48a82405:8100::/32CloudflareIP
49db60758c8344959c338a74afc9748a2405:b500::/32CloudflareIP
96e9eca1923c40d5a84865145f5a5d6a2606:4700::/32CloudflareIP
21bc52a26e10405d89b7180ddcf493022803:f800::/32CloudflareIP
ff78f842188e4b869eb5389ae9ab8f412a06:98c0::/29CloudflareIP
0880cdfc40b14f6fa0639522a728859d2c0f:f248::/32CloudflareIP
$Theresult_info.cursorsarea also contains abeforevalue for reverse scrolling.As withper_pagescrolling, raw mode is used.DNSSEC CLI calls$cli4/zones/:example.com/dnssec|jq-c'{"status":.status}'{"status":"disabled"}$

$cli4--patchstatus=active/zones/:example.com/dnssec|jq-c'{"status":.status}'{"status":"pending"}$

$cli4/zones/:example.com/dnssec{"algorithm":"13","digest":"41600621c65065b09230ebc9556ced937eb7fd86e31635d0025326ccf09a7194","digest_algorithm":"SHA256","digest_type":"2","ds":"example.com. 3600 IN DS 2371 13 2 41600621c65065b09230ebc9556ced937eb7fd86e31635d0025326ccf09a7194","flags":257,"key_tag":2371,"key_type":"ECDSAP256SHA256","modified_on":"2016-05-01T22:42:15.591158Z","public_key":"mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==","status":"pending"}$Zone file upload (i.e. import) CLI calls (uses BIND format files)Refer toImport DNS recordson API documentation for this feature.$catzone.txt
example.com.INSOAsomewhere.example.com.someone.example.com.(20170101013H151w3h)record1.example.com.INA10.0.0.1
record2.example.com.INAAAA2001:d8b::2
record3.example.com.INCNAMErecord1.example.com.
record4.example.com.INTXT"some text"$

$cli4--postfile=@zone.txt/zones/:example.com/dns_records/import{"recs_added":4,"total_records_parsed":4}$Zone file upload (i.e. import) Python calls (uses BIND format files)Becauseimportis a keyword (or reserved word) in Python we append a_(underscore) to the verb in order to use.
Thecli4command does not need this edit.## "import" is a reserved word and hence we add '_' to the end of verb.#r=cf.zones.dns_records.import_.post(zone_id,files={'file':fd})Seeexamples/example_dns_import.pyfor working code.Zone file download (i.e. export) CLI calls (uses BIND format files)The following is documented within theAdvancedoption of the DNS page within the Cloudflare portal.$cli4/zones/:example.com/dns_records/export|egrep-v'^;;|^$'$ORIGIN.
@3600INSOAexample.com.root.example.com.(2025552311;serial7200;refresh3600;retry86400;expire3600);minimum
example.com.300INNSREPLACE&ME$WITH^YOUR@NAMESERVER.
record4.example.com.300INTXT"some text"record3.example.com.300INCNAMErecord1.example.com.
record1.example.com.300INA10.0.0.1
record2.example.com.300INAAAA2001:d8b::2
$The egrep is used for documentation brevity.This can also be done via Python code with the following example.#!/usr/bin/env pythonimportsysimportCloudFlaredefmain():zone_name=sys.argv[1]cf=CloudFlare.CloudFlare()zones=cf.zones.get(params={'name':zone_name})zone_id=zones[0]['id']dns_records=cf.zones.dns_records.export.get(zone_id)forlindns_records.splitlines():iflen(l)==0orl[0]==';':continueprint(l)exit(0)if__name__=='__main__':main()Cloudflare WorkersCloudflare Workers are described on the Cloudflare blog athereandhere, with the beta release announcedhere.The Python libraries now support the Cloudflare Workers API calls. The following javascript is lifted fromhttps://cloudflareworkers.com/and slightly modified.$catmodify-body.js
addEventListener("fetch",event=>{event.respondWith(fetchAndModify(event.request));});asyncfunctionfetchAndModify(request){console.log("got a request:",request);//Sendtherequestontotheoriginserver.constresponse=awaitfetch(request);//Readresponsebody.consttext=awaitresponse.text();//Modifyit.constmodified=text.replace("<body>","<body style=\"background: #ff0;\">");//Returnmodifiedresponse.returnnewResponse(modified,{status:response.status,statusText:response.statusText,headers:response.headers});}$Here's the website with it's simple<body>statement$curl-sShttps://example.com/|fgrep'<body'<body>
$Now lets add the script. Looking above, you will see that it's simple action is to modify the<body>statement and make the background yellow.$cli4--put@-/zones/:example.com/workers/script<modify-body.js{"etag":"1234567890123456789012345678901234567890123456789012345678901234","id":"example-com","modified_on":"2018-02-15T00:00:00.000000Z","script":"addEventListener(\"fetch\", event => {\n  event.respondWith(fetchAndModify(event.request));\n});\n\nasync function fetchAndModify(request) {\n  console.log(\"got a request:\", request);\n\n  // Send the request on to the origin server.\n  const response = await fetch(request);\n\n  // Read response body.\n  const text = await response.text();\n\n  // Modify it.\n  const modified = text.replace(\n  \"<body>\",\n  \"<body style=\\\"background: #ff0;\\\">\");\n\n  // Return modified response.\n  return new Response(modified, {\n    status: response.status,\n    statusText: response.statusText,\n    headers: response.headers\n  });\n}\n","size":603}$The following call checks that the script is associated with the zone. In this case, it's the only script added by this user.$cli4/user/workers/scripts[{"created_on":"2018-02-15T00:00:00.000000Z","etag":"1234567890123456789012345678901234567890123456789012345678901234","id":"example-com","modified_on":"2018-02-15T00:00:00.000000Z"}]$Next step is to make sure a route is added for that script on that zone.$cli4--postpattern="example.com/*"script="example-com"/zones/:example.com/workers/routes{"id":"12345678901234567890123456789012"}$

$cli4/zones/:example.com/workers/routes[{"id":"12345678901234567890123456789012","pattern":"example.com/*","script":"example-com"}]$With that script added to the zone and the route added, we can now see the website has been modified because of the Cloudflare Worker.$curl-sShttps://example.com/|fgrep'<body'<bodystyle="background: #ff0;">
$All this can be removed; hence bringing the website back to its initial state.$cli4--delete/zones/:example.com/workers/script12345678901234567890123456789012$cli4--delete/zones/:example.com/workers/routes/:12345678901234567890123456789012true$

$curl-sShttps://example.com/|fgrep'<body'<body>
$Refer to the Cloudflare Workers API documentation for more information.Cloudflare Instant LogsPlease seehttps://developers.cloudflare.com/logs/instant-logsfor all the information on how to use this feature.
Thecli4command along with the Python libaries can be used to control the instant logs; however, the websocket reading is outside the scope of this library.To query the states of the instant logs:$cli4/zones/:example.com/logpush/edge/jobs|jq.[]$To add monitoring:$cli4--post\='{"fields": "ClientIP,ClientRequestHost,ClientRequestMethod,ClientRequestURI,EdgeEndTimestamp,EdgeResponseBytes,EdgeResponseStatus,EdgeStartTimestamp,RayID","sample": 1,"filter": "","kind": "instant-logs"}'\/zones/:example.com/logpush/edge/jobs|jq.{"destination_conf":"wss://logs.cloudflare.com/instant-logs/ws/sessions/00000000000000000000000000000000","fields":"ClientIP,ClientRequestHost,ClientRequestMethod,ClientRequestURI,EdgeEndTimestamp,EdgeResponseBytes,EdgeResponseStatus,EdgeStartTimestamp,RayID","filter":"","kind":"instant-logs","sample":1,"session_id":"00000000000000000000000000000000"}$To see the results:$cli4/zones/:example.com/logpush/edge/jobs|jq.[{"fields":"ClientIP,ClientRequestHost,ClientRequestMethod,ClientRequestURI,EdgeEndTimestamp,EdgeResponseBytes,EdgeResponseStatus,EdgeStartTimestamp,RayID","filter":"","kind":"instant-logs","sample":1,"session_id":"00000000000000000000000000000000"}]$Cloudflare GraphQLThe GraphQL interface can be accessed via the command line or via Python.query="""
      query {
        viewer {
            zones(filter: {zoneTag: "%s"} ) {
            httpRequests1dGroups(limit:40, filter:{date_lt: "%s", date_gt: "%s"}) {
              sum { countryMap { bytes, requests, clientCountryName } }
              dimensions { date }
            }
          }
        }
      }
    """ % (zone_id, date_before[0:10], date_after[0:10])

    r = cf.graphql.post(data={'query':query})

    httpRequests1dGroups = zone_info = r['data']['viewer']['zones'][0]['httpRequests1dGroups']See theexamples/example_graphql.shandexamples/example_graphql.pyfiles for working examples.
Here is the working example of the shell version:$examples/example_graphql.shexample.com2020-07-14T02:00:00Z348802020-07-14T03:00:00Z189532020-07-14T04:00:00Z287002020-07-14T05:00:00Z23582020-07-14T06:00:00Z349052020-07-14T07:00:00Z7792020-07-14T08:00:00Z354502020-07-14T10:00:00Z178032020-07-14T11:00:00Z326782020-07-14T12:00:00Z199472020-07-14T13:00:00Z49562020-07-14T14:00:00Z345852020-07-14T15:00:00Z30222020-07-14T16:00:00Z52242020-07-14T18:00:00Z794822020-07-14T21:00:00Z106092020-07-14T22:00:00Z57402020-07-14T23:00:00Z25452020-07-15T01:00:00Z10777$For more information on how to use GraphQL at Cloudflare, refer to theCloudflare GraphQL Analytics API.
It contains a full overview of Cloudflare's GraphQL features and keywords.Cloudflare AISeehttps://blog.cloudflare.com/workers-ai-update-stable-diffusion-code-llama-workers-ai-in-100-cities/for the introduction,
along withhttps://developers.cloudflare.com/workers-ai/models/for the nitty gritty details.There are three AI calls included within the example folder.Image creation.$pythonexamples/example_ai_images.pyAhappyllamarunningthroughanorangecloud>/tmp/image.png
$
$file/tmp/image.png
/tmp/image.png:PNGimagedata,1024x1024,8-bit/colorRGB,non-interlaced
$Translation.$pythonexamples/example_ai_translate.pyI\'llhaveanorderofthemoulefrites
Jevaisavoirunecommandedesfritesdemoule
$Speech Recognition with the openai/whisper model.The following downloads a speech as an mp3 file and passes it to the AI API.
It does a very good job transcribing; however, there's a good chance these mp3 files were use for training.
That said, the example code is here to show how the API works vs testing the AI/ML quality.$pythonexamples/example_ai_speechrecognition.py
mp3received:length=700367MyfellowAmericans,MichelleandIhavebeensotouchedbyallthewellwishesthatwe've received over the past few weeks. But tonight, tonight it'smyturntosaythanks.
$This is presently work-in-progress because of the non-Python calling method. The syntax could change in the future.They can also be called viacli4.$cli4--image--posttext="I'll have an order of the moule frites"source_lang=englishtarget_lang=french/accounts/:AccountID/ai/run/@cf/meta/m2m100-1.2b{'translated_text':'Je vais avoir une commande des frites de moule'}$Presently you will need the following in yourcloudflare.cfgfile.$cat~/.cloudflare/cloudflare.cfg[CloudFlare]global_request_timeout=120max_request_retries=1extras=/accounts/:id/ai/run/@cf/meta/llama-2-7b-chat-fp16/accounts/:id/ai/run/@cf/meta/llama-2-7b-chat-int8/accounts/:id/ai/run/@cf/mistral/mistral-7b-instruct-v0.1/accounts/:id/ai/run/@cf/openai/whisper/accounts/:id/ai/run/@cf/meta/m2m100-1.2b/accounts/:id/ai/run/@cf/huggingface/distilbert-sst-2-int8/accounts/:id/ai/run/@cf/microsoft/resnet-50/accounts/:id/ai/run/@cf/stabilityai/stable-diffusion-xl-base-1.0/accounts/:id/ai/run/@cf/baai/bge-base-en-v1.5/accounts/:id/ai/run/@cf/baai/bge-large-en-v1.5/accounts/:id/ai/run/@cf/baai/bge-small-en-v1.5

$As the@(at) symbol and the.(dot) symbol aren't allowed in python variable names; you'll have the replace@cfwithat_cfand.with_.
There's already notes above that state that-(dash) is replaced with_in the code.
That will be needed with some model names.Thecli4command does not need this edit. It is done on the fly!For example, the following code is valid:r=cf.accounts.ai.run.at_cf.openai.whisper.post(account_id,data=audio_data)r=cf.accounts.ai.run.at_cf.meta.m2m100_1_2b.post(account_id,data=translate_data)r=cf.accounts.ai.run.at_cf.stabilityai.stable_diffusion_xl_base_1_0.post(account_id,data=image_create_data)Or you can use thefind()call can will do this conversion for you.translate_data={'text':"I'll have an order of the moule frites",'source_lang':'english','target_lang':'french'}m=cf.find('/accounts/:id/ai/run/@cf/meta/m2m100-1.2b')r=m.post(account_id,data=translate_data)print(r['translated_text'])You will also have to run with a version of the library above2.18.2.Implemented API callsThe--dumpargument to cli4 will produce a list of all the call implemented within the library.$cli4--dump
/certificates
/ips
/organizations
...
/zones/ssl/analyze
/zones/ssl/certificate_packs
/zones/ssl/verification
$Table of commandsAn automatically generated table of commands is providedhere.Adding extra API calls manuallyExtra API calls can be added via the configuration file$cat~/.cloudflare/cloudflare.cfg[Cloudflare]extras=/client/v4/command/client/v4/command/:command_identifier/client/v4/command/:command_identifier/settings
$While it's easy to call anything within Cloudflare's API, it's not very useful to add items in here as they will simply return API URL errors.
Technically, this is only useful for internal testing within Cloudflare.IssuesThe following error can be caused by an out of date SSL/TLS library and/or out of date Python./usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/util/ssl_.py:318:SNIMissingWarning:AnHTTPSrequesthasbeenmade,buttheSNI(SubjectNameIndication)extensiontoTLSisnotavailableonthisplatform.ThismaycausetheservertopresentanincorrectTLScertificate,whichcancausevalidationfailures.YoucanupgradetoanewerversionofPythontosolvethis.Formoreinformation,seehttps://urllib3.readthedocs.org/en/latest/security.html#snimissingwarning.SNIMissingWarning
/usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/util/ssl_.py:122:InsecurePlatformWarning:AtrueSSLContextobjectisnotavailable.Thispreventsurllib3fromconfiguringSSLappropriatelyandmaycausecertainSSLconnectionstofail.YoucanupgradetoanewerversionofPythontosolvethis.Formoreinformation,seehttps://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.InsecurePlatformWarningThe solution can be foundhereand/orhere.Python 2.x vs 3.x supportAs of May/June 2016 the code is now tested against pylint.
This was required in order to move the codebase into Python 3.x.
The motivation for this came fromDanielle Madeley (danni).While the codebase has been edited to run on Python 3.x, there's not been enough Python 3.x testing performed.If you can help in this regard; please contact the maintainers.As of January 2020 the code is Python3 clean.As of January 2020 the code is shipped up to pypi with Python2 support removed.As of January 2020 the code is Python3.8 clean. The newSyntaxWarningmessages (i.e.SyntaxWarning: "is" with a literal. Did you mean "=="?) meant minor edits were needed.As of late 2023 the code is Python3.11 clean.pypi and GitHub signed releasesAs of October/2022, the code is signed by the maintainers personal email address:mahtin@mahtin.com7EA1 39C4 0C1C 842F 9D41 AAF9 4A34 925D 0517 2859CreditThis is based on work byFelix Wong (gnowxilef)foundhere.
It has been seriously expanded upon.ChangelogAn automatically generated CHANGELOG is providedhere.CopyrightPortions copyrightFelix Wong (gnowxilef)2015 and Cloudflare 2016 thru 2023.