• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..03-May-2022-

CloudFlare/H01-Jan-2021-1,9681,456

cli4/H01-Jan-2021-741621

cloudflare.egg-info/H03-May-2022-1,4661,137

examples/H01-Jan-2021-1,4961,091

LICENSEH A D20-Nov-20191.1 KiB2217

MANIFEST.inH A D20-Nov-201983 43

PKG-INFOH A D01-Jan-202171.1 KiB1,4661,137

README.rstH A D31-Dec-202059 KiB1,4431,115

setup.cfgH A D01-Jan-202138 53

setup.pyH A D03-May-20222.1 KiB6145

README.rst

1cloudflare-python
2=================
3
4Installation
5------------
6
7Two methods are provided to install this software. Use PyPi (see
8`package <https://pypi.python.org/pypi/cloudflare>`__ details) or GitHub
9(see `package <https://github.com/cloudflare/python-cloudflare>`__
10details).
11
12Via PyPI
13~~~~~~~~
14
15.. code:: bash
16
17        $ sudo pip install cloudflare
18        $
19
20Yes - that simple! (the sudo may not be needed in some cases).
21
22Via github
23~~~~~~~~~~
24
25.. code:: bash
26
27        $ git clone https://github.com/cloudflare/python-cloudflare
28        $ cd python-cloudflare
29        $ ./setup.py build
30        $ sudo ./setup.py install
31        $
32
33Or whatever variance of that you want to use. There is a Makefile
34included.
35
36Cloudflare name change - dropping the capital F
37-----------------------------------------------
38
39In Sepember/October 2016 the company modified its company name and
40dropped the capital F. However, for now (and for backward compatibility
41reasons) the class name stays the same.
42
43Cloudflare API version 4
44------------------------
45
46The Cloudflare API can be found `here <https://api.cloudflare.com/>`__.
47Each API call is provided via a similarly named function within the
48**CloudFlare** class. A full list is provided below.
49
50Example code
51------------
52
53All example code is available on GitHub (see
54`package <https://github.com/cloudflare/python-cloudflare>`__ in the
55`examples <https://github.com/cloudflare/python-cloudflare/tree/master/examples>`__
56folder).
57
58Blog
59----
60
61This package was initially introduced
62`here <https://blog.cloudflare.com/python-cloudflare/>`__ via
63Cloudflare's `blog <https://blog.cloudflare.com/>`__.
64
65Getting Started
66---------------
67
68A very simple listing of zones within your account; including the IPv6
69status of the zone.
70
71.. code:: python
72
73    import CloudFlare
74
75    def main():
76        cf = CloudFlare.CloudFlare()
77        zones = cf.zones.get()
78        for zone in zones:
79            zone_id = zone['id']
80            zone_name = zone['name']
81            print zone_id, zone_name
82
83    if __name__ == '__main__':
84        main()
85
86This example works when there are less than 50 zones (50 is the default
87number of values returned from a query like this).
88
89Now lets expand on that and add code to show the IPv6 and SSL status of
90the zones. Lets also query 100 zones.
91
92.. code:: python
93
94    import CloudFlare
95
96    def main():
97        cf = CloudFlare.CloudFlare()
98        zones = cf.zones.get(params = {'per_page':100})
99        for zone in zones:
100            zone_id = zone['id']
101            zone_name = zone['name']
102
103            settings_ssl = cf.zones.settings.ssl.get(zone_id)
104            ssl_status = settings_ssl['value']
105
106            settings_ipv6 = cf.zones.settings.ipv6.get(zone_id)
107            ipv6_status = settings_ipv6['value']
108
109            print zone_id, zone_name, ssl_status, ipv6_status
110
111    if __name__ == '__main__':
112        main()
113
114In order to query more than a single page of zones, we would have to use
115the raw mode (described more below). We can loop over many get calls and
116pass the page parameter to facilitate the paging.
117
118Raw mode is only needed when a get request has the possibility of
119returning many items.
120
121.. code:: python
122
123    import CloudFlare
124
125    def main():
126        cf = CloudFlare.CloudFlare(raw=True)
127        page_number = 0
128        while True:
129            page_number += 1
130            raw_results = cf.zones.get(params={'per_page':5,'page':page_number})
131            zones = raw_results['result']
132
133            for zone in zones:
134                zone_id = zone['id']
135                zone_name = zone['name']
136                print zone_id, zone_name
137
138            total_pages = raw_results['result_info']['total_pages']
139            if page_number == total_pages:
140                break
141
142    if __name__ == '__main__':
143        main()
144
145A more complex example follows.
146
147.. code:: python
148
149    import CloudFlare
150
151    def main():
152        zone_name = 'example.com'
153
154        cf = CloudFlare.CloudFlare()
155
156        # query for the zone name and expect only one value back
157        try:
158            zones = cf.zones.get(params = {'name':zone_name,'per_page':1})
159        except CloudFlare.exceptions.CloudFlareAPIError as e:
160            exit('/zones.get %d %s - api call failed' % (e, e))
161        except Exception as e:
162            exit('/zones.get - %s - api call failed' % (e))
163
164        if len(zones) == 0:
165            exit('No zones found')
166
167        # extract the zone_id which is needed to process that zone
168        zone = zones[0]
169        zone_id = zone['id']
170
171        # request the DNS records from that zone
172        try:
173            dns_records = cf.zones.dns_records.get(zone_id)
174        except CloudFlare.exceptions.CloudFlareAPIError as e:
175            exit('/zones/dns_records.get %d %s - api call failed' % (e, e))
176
177        # print the results - first the zone name
178        print zone_id, zone_name
179
180        # then all the DNS records for that zone
181        for dns_record in dns_records:
182            r_name = dns_record['name']
183            r_type = dns_record['type']
184            r_value = dns_record['content']
185            r_id = dns_record['id']
186            print '\t', r_id, r_name, r_type, r_value
187
188        exit(0)
189
190    if __name__ == '__main__':
191        main()
192
193Providing Cloudflare Username and API Key
194-----------------------------------------
195
196When you create a **CloudFlare** class you can pass up to four
197parameters.
198
199-  API Token or API Key
200-  Account email (only if an API Key is being used)
201-  Optional Origin-CA Certificate Token
202-  Optional Debug flag (True/False)
203-  Optional Profile name (the default is ``Cloudflare``)
204
205.. code:: python
206
207    import CloudFlare
208
209        # A minimal call - reading values from environment variables or configuration file
210        cf = CloudFlare.CloudFlare()
211
212        # A minimal call with debug enabled
213        cf = CloudFlare.CloudFlare(debug=True)
214
215        # An authenticated call using an API Token (note the missing email)
216        cf = CloudFlare.CloudFlare(token='00000000000000000000000000000000')
217
218        # An authenticated call using an API Key
219        cf = CloudFlare.CloudFlare(email='user@example.com', token='00000000000000000000000000000000')
220
221        # An authenticated call using an API Key and CA-Origin info
222        cf = CloudFlare.CloudFlare(email='user@example.com', token='00000000000000000000000000000000', certtoken='v1.0-...')
223
224        # An authenticated call using using a stored profile (see below)
225        cf = CloudFlare.CloudFlare(profile="CompanyX"))
226
227If the account email and API key are not passed when you create the
228class, then they are retrieved from either the users exported shell
229environment variables or the .cloudflare.cfg or ~/.cloudflare.cfg or
230~/.cloudflare/cloudflare.cfg files, in that order.
231
232If you're using an API Token, any ``cloudflare.cfg`` file must either
233not contain an ``email`` attribute or be a zero length string and the
234``CF_API_EMAIL`` environment variable must be unset or be a zero length
235string, otherwise the token will be treated as a key and will throw an
236error.
237
238There is one call that presently doesn't need any email or token
239certification (the */ips* call); hence you can test without any values
240saved away.
241
242Using shell environment variables
243~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
244
245.. code:: bash
246
247    $ export CF_API_EMAIL='user@example.com' # Do not set if using an API Token
248    $ export CF_API_KEY='00000000000000000000000000000000'
249    $ export CF_API_CERTKEY='v1.0-...'
250    $
251
252These are optional environment variables; however, they do override the
253values set within a configuration file.
254
255Using configuration file to store email and keys
256~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
257
258.. code:: bash
259
260    $ cat ~/.cloudflare/cloudflare.cfg
261    [CloudFlare]
262    email = user@example.com # Do not set if using an API Token
263    token = 00000000000000000000000000000000
264    certtoken = v1.0-...
265    extras =
266    $
267
268More than one profile can be stored within that file. Here's an example
269for a work and home setup (in this example work has an API Token and
270home uses email/token).
271
272.. code:: bash
273
274    $ cat ~/.cloudflare/cloudflare.cfg
275    [Work]
276    token = 00000000000000000000000000000000
277    [Home]
278    email = home@example.com
279    token = 00000000000000000000000000000000
280    $
281
282To select a profile, use the ``--profile profile-name`` option for
283``cli4`` command or use ``profile="profile-name"`` in the library call.
284
285.. code:: bash
286
287    $ cli4 --profile Work /zones | jq '.[]|.name' | wc -l
288          13
289    $
290
291    $ cli4 --profile Home /zones | jq '.[]|.name' | wc -l
292           1
293    $
294
295Here is the same in code.
296
297.. code:: python
298
299    #!/usr/bin/env python
300
301    import CloudFlare
302
303    def main():
304        cf = CloudFlare.CloudFlare(profile="Work")
305        ...
306
307Advanced use of configuration file for authentication based on method
308~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
309
310The configuration file can have values that are both generic and
311specific to the method. Here's an example where a project has a
312different API Token for reading and writing values.
313
314.. code:: bash
315
316    $ cat ~/.cloudflare/cloudflare.cfg
317    [Work]
318    token = 0000000000000000000000000000000000000000
319    token.get = 0123456789012345678901234567890123456789
320    $
321
322When a GET call is processed then the second token is used. For all
323other calls the first token is used. Here's a more explict verion of
324that config:
325
326.. code:: bash
327
328    $ cat ~/.cloudflare/cloudflare.cfg
329    [Work]
330    token.delete = 0000000000000000000000000000000000000000
331    token.get = 0123456789012345678901234567890123456789
332    token.patch = 0000000000000000000000000000000000000000
333    token.post = 0000000000000000000000000000000000000000
334    token.put = 0000000000000000000000000000000000000000
335    $
336
337This can be used with email values also.
338
339About /certificates and certtoken
340~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
341
342The *CF\_API\_CERTKEY* or *certtoken* values are used for the Origin-CA
343*/certificates* API calls. You can leave *certtoken* in the
344configuration with a blank value (or omit the option variable fully).
345
346The *extras* values are used when adding API calls outside of the core
347codebase. Technically, this is only useful for internal testing within
348Cloudflare. You can leave *extras* in the configuration with a blank
349value (or omit the option variable fully).
350
351Exceptions and return values
352----------------------------
353
354Response data
355~~~~~~~~~~~~~
356
357The response is build from the JSON in the API call. It contains the
358**results** values; but does not contain the paging values.
359
360You can return all the paging values by calling the class with raw=True.
361Here's an example without paging.
362
363.. code:: python
364
365    #!/usr/bin/env python
366
367    import json
368    import CloudFlare
369
370    def main():
371        cf = CloudFlare.CloudFlare()
372        zones = cf.zones.get(params={'per_page':5})
373        print len(zones)
374
375    if __name__ == '__main__':
376        main()
377
378The results are as follows.
379
380::
381
382    5
383
384When you add the raw option; the APIs full structure is returned. This
385means the paging values can be seen.
386
387.. code:: python
388
389    #!/usr/bin/env python
390
391    import json
392    import CloudFlare
393
394    def main():
395        cf = CloudFlare.CloudFlare(raw=True)
396        zones = cf.zones.get(params={'per_page':5})
397        print zones.length()
398        print json.dumps(zones, indent=4, sort_keys=True)
399
400    if __name__ == '__main__':
401        main()
402
403This produces.
404
405::
406
407    5
408    {
409        "result": [
410            ...
411        ],
412        "result_info": {
413            "count": 5,
414            "page": 1,
415            "per_page": 5,
416            "total_count": 31,
417            "total_pages": 7
418        }
419    }
420
421A full example of paging is provided below.
422
423Exceptions
424~~~~~~~~~~
425
426The library will raise **CloudFlareAPIError** when the API call fails.
427The exception returns both an integer and textual message in one value.
428
429.. code:: python
430
431    import CloudFlare
432
433        ...
434        try
435            r = ...
436        except CloudFlare.exceptions.CloudFlareAPIError as e:
437            exit('api error: %d %s' % (e, e))
438        ...
439
440The other raised response is **CloudFlareInternalError** which can
441happen when calling an invalid method.
442
443In some cases more than one error is returned. In this case the return
444value **e** is also an array. You can iterate over that array to see the
445additional error.
446
447.. code:: python
448
449    import sys
450    import CloudFlare
451
452        ...
453        try
454            r = ...
455        except CloudFlare.exceptions.CloudFlareAPIError as e:
456            if len(e) > 0:
457                sys.stderr.write('api error - more than one error value returned!\n')
458                for x in e:
459                    sys.stderr.write('api error: %d %s\n' % (x, x))
460            exit('api error: %d %s' % (e, e))
461        ...
462
463Exception examples
464~~~~~~~~~~~~~~~~~~
465
466Here's examples using the CLI command cli4 of the responses passed back
467in exceptions.
468
469First a simple get with a clean (non-error) response.
470
471::
472
473    $ cli4 /zones/:example.com/dns_records | jq -c '.[]|{"name":.name,"type":.type,"content":.content}'
474    {"name":"example.com","type":"MX","content":"something.example.com"}
475    {"name":"something.example.com","type":"A","content":"10.10.10.10"}
476    $
477
478Next a simple/single error response. This is simulated by providing
479incorrect authentication information.
480
481::
482
483    $ CF_API_EMAIL='someone@example.com' cli4 /zones/
484    cli4: /zones - 9103 Unknown X-Auth-Key or X-Auth-Email
485    $
486
487More than one call can be done on the same command line. In this mode,
488the connection is preserved between calls.
489
490::
491
492    $ cli4 /user/organizations /user/invites
493    ...
494    $
495
496Note that the output is presently two JSON structures one after the
497other - so less useful that you may think.
498
499Finally, a command that provides more than one error response. This is
500simulated by passing an invalid IPv4 address to a DNS record creation.
501
502::
503
504    $ cli4 --post name='foo' type=A content="1" /zones/:example.com/dns_records
505    cli4: /zones/:example.com/dns_records - 9005 Content for A record is invalid. Must be a valid IPv4 address
506    cli4: /zones/:example.com/dns_records - 1004 DNS Validation Error
507    $
508
509Included example code
510---------------------
511
512The
513`examples <https://github.com/cloudflare/python-cloudflare/tree/master/examples>`__
514folder contains many examples in both simple and verbose formats.
515
516A DNS zone code example
517-----------------------
518
519.. code:: python
520
521    #!/usr/bin/env python
522
523    import sys
524    import CloudFlare
525
526    def main():
527        zone_name = sys.argv[1]
528        cf = CloudFlare.CloudFlare()
529        zone_info = cf.zones.post(data={'jump_start':False, 'name': zone_name})
530        zone_id = zone_info['id']
531
532        dns_records = [
533            {'name':'foo', 'type':'AAAA', 'content':'2001:d8b::1'},
534            {'name':'foo', 'type':'A', 'content':'192.168.0.1'},
535            {'name':'duh', 'type':'A', 'content':'10.0.0.1', 'ttl':120},
536            {'name':'bar', 'type':'CNAME', 'content':'foo'},
537            {'name':'shakespeare', 'type':'TXT', 'content':"What's in a name? That which we call a rose by any other name ..."}
538        ]
539
540        for dns_record in dns_records:
541            r = cf.zones.dns_records.post(zone_id, data=dns_record)
542        exit(0)
543
544    if __name__ == '__main__':
545        main()
546
547A DNS zone delete code example (be careful)
548-------------------------------------------
549
550.. code:: python
551
552    #!/usr/bin/env python
553
554    import sys
555    import CloudFlare
556
557    def main():
558        zone_name = sys.argv[1]
559        cf = CloudFlare.CloudFlare()
560        zone_info = cf.zones.get(params={'name': zone_name})
561        zone_id = zone_info['id']
562
563        dns_name = sys.argv[2]
564        dns_records = cf.zones.dns_records.get(zone_id, params={'name':dns_name + '.' + zone_name})
565        for dns_record in dns_records:
566            dns_record_id = dns_record['id']
567            r = cf.zones.dns_records.delete(zone_id, dns_record_id)
568        exit(0)
569
570    if __name__ == '__main__':
571        main()
572
573CLI
574---
575
576All API calls can be called from the command line. The command will
577convert domain names prefixed with a colon (``:``) into
578zone\_identifiers: e.g. to view ``example.com`` you must use
579``cli4 /zones/:example.com`` (the zone ID cannot be used).
580
581.. code:: bash
582
583    $ cli4 [-V|--version] [-h|--help] [-v|--verbose] [-q|--quiet] [-j|--json] [-y|--yaml] [-r|--raw] [-d|--dump] [--get|--patch|--post|--put|--delete] [item=value ...] /command...
584
585CLI parameters for POST/PUT/PATCH
586~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
587
588For API calls that need to pass data or parameters there is various
589formats to use.
590
591The simplest form is ``item=value``. This passes the value as a string
592within the APIs JSON data.
593
594If you need a numeric value passed then **==** can be used to force the
595value to be treated as a numeric value within the APIs JSON data. For
596example: ``item==value``.
597
598if you need to pass a list of items; then **[]** can be used. For
599example:
600
601::
602
603    pool_id1="11111111111111111111111111111111"
604    pool_id2="22222222222222222222222222222222"
605    pool_id3="33333333333333333333333333333333"
606    cli4 --post global_pools="[ ${pool_id1}, ${pool_id2}, ${pool_id3} ]" region_pools="[ ]" /user/load_balancers/maps
607
608Data or parameters can be either named or unnamed. It can not be both.
609Named is the majority format; as described above. Unnamed parameters
610simply don't have anything before the **=** sign, as in ``=value``. This
611format is presently only used by the Cloudflare Load Balancer API calls.
612For example:
613
614::
615
616    cli4 --put ="00000000000000000000000000000000" /user/load_balancers/maps/:00000000000000000000000000000000/region/:WNAM
617
618Data can also be uploaded from file contents. Using the
619``item=@filename`` format will open the file and the contents uploaded
620in the POST.
621
622CLI output
623~~~~~~~~~~
624
625The output from the CLI command is in JSON or YAML format (and human
626readable). This is controled by the **--yaml** or **--json** flags (JSON
627is the default).
628
629Simple CLI examples
630~~~~~~~~~~~~~~~~~~~
631
632-  ``cli4 /user/billing/profile``
633-  ``cli4 /user/invites``
634
635-  ``cli4 /zones/:example.com``
636-  ``cli4 /zones/:example.com/dnssec``
637-  ``cli4 /zones/:example.com/settings/ipv6``
638-  ``cli4 --put /zones/:example.com/activation_check``
639-  ``cli4 /zones/:example.com/keyless_certificates``
640
641-  ``cli4 /zones/:example.com/analytics/dashboard``
642
643More complex CLI examples
644~~~~~~~~~~~~~~~~~~~~~~~~~
645
646Here is the creation of a DNS entry, followed by a listing of that entry
647and then the deletion of that entry.
648
649.. code:: bash
650
651    $ $ cli4 --post name="test" type="A" content="10.0.0.1" /zones/:example.com/dns_records
652    {
653        "id": "00000000000000000000000000000000",
654        "name": "test.example.com",
655        "type": "A",
656        "content": "10.0.0.1",
657        ...
658    }
659    $
660
661    $ cli4 /zones/:example.com/dns_records/:test.example.com | jq '{"id":.id,"name":.name,"type":.type,"content":.content}'
662    {
663      "id": "00000000000000000000000000000000",
664      "name": "test.example.com",
665      "type": "A",
666      "content": "10.0.0.1"
667    }
668
669    $ cli4 --delete /zones/:example.com/dns_records/:test.example.com | jq -c .
670    {"id":"00000000000000000000000000000000"}
671    $
672
673There's the ability to handle dns entries with multiple values. This
674produces more than one API call within the command.
675
676::
677
678    $ cli4 /zones/:example.com/dns_records/:test.example.com | jq -c '.[]|{"id":.id,"name":.name,"type":.type,"content":.content}'
679    {"id":"00000000000000000000000000000000","name":"test.example.com","type":"A","content":"192.168.0.1"}
680    {"id":"00000000000000000000000000000000","name":"test.example.com","type":"AAAA","content":"2001:d8b::1"}
681    $
682
683Here are the cache purging commands.
684
685.. code:: bash
686
687    $ cli4 --delete purge_everything=true /zones/:example.com/purge_cache | jq -c .
688    {"id":"00000000000000000000000000000000"}
689    $
690
691    $ cli4 --delete files='[http://example.com/css/styles.css]' /zones/:example.com/purge_cache | jq -c .
692    {"id":"00000000000000000000000000000000"}
693    $
694
695    $ cli4 --delete files='[http://example.com/css/styles.css,http://example.com/js/script.js]' /zones/:example.com/purge_cache | jq -c .
696    {"id":"00000000000000000000000000000000"}
697    $
698
699    $ cli4 --delete tags='[tag1,tag2,tag3]' /zones/:example.com/purge_cache | jq -c .
700    cli4: /zones/:example.com/purge_cache - 1107 Only enterprise zones can purge by tag.
701    $
702
703A somewhat useful listing of available plans for a specific zone.
704
705.. code:: bash
706
707    $ cli4 /zones/:example.com/available_plans | jq -c '.[]|{"id":.id,"name":.name}'
708    {"id":"00000000000000000000000000000000","name":"Pro Website"}
709    {"id":"00000000000000000000000000000000","name":"Business Website"}
710    {"id":"00000000000000000000000000000000","name":"Enterprise Website"}
711    {"id":"0feeeeeeeeeeeeeeeeeeeeeeeeeeeeee","name":"Free Website"}
712    $
713
714Cloudflare CA CLI examples
715~~~~~~~~~~~~~~~~~~~~~~~~~~
716
717Here's some Cloudflare CA examples. Note the need of the zone\_id=
718parameter with the basic **/certificates** call.
719
720.. code:: bash
721
722    $ cli4 /zones/:example.com | jq -c '.|{"id":.id,"name":.name}'
723    {"id":"12345678901234567890123456789012","name":"example.com"}
724    $
725
726    $ cli4 zone_id=12345678901234567890123456789012 /certificates | jq -c '.[]|{"id":.id,"expires_on":.expires_on,"hostnames":.hostnames,"certificate":.certificate}'
727    {"id":"123456789012345678901234567890123456789012345678","expires_on":"2032-01-29 22:36:00 +0000 UTC","hostnames":["*.example.com","example.com"],"certificate":"-----BEGIN CERTIFICATE-----\n ... "}
728    {"id":"123456789012345678901234567890123456789012345678","expires_on":"2032-01-28 23:23:00 +0000 UTC","hostnames":["*.example.com","example.com"],"certificate":"-----BEGIN CERTIFICATE-----\n ... "}
729    {"id":"123456789012345678901234567890123456789012345678","expires_on":"2032-01-28 23:20:00 +0000 UTC","hostnames":["*.example.com","example.com"],"certificate":"-----BEGIN CERTIFICATE-----\n ... "}
730    $
731
732A certificate can be viewed via a simple GET request.
733
734.. code:: bash
735
736    $ cli4 /certificates/:123456789012345678901234567890123456789012345678
737    {
738        "certificate": "-----BEGIN CERTIFICATE-----\n ... ",
739        "expires_on": "2032-01-29 22:36:00 +0000 UTC",
740        "hostnames": [
741            "*.example.com",
742            "example.com"
743        ],
744        "id": "123456789012345678901234567890123456789012345678",
745        "request_type": "origin-rsa"
746    }
747    $
748
749Creating a certificate. This is done with a **POST** request. Note the
750use of **==** in order to pass a decimal number (vs. string) in JSON.
751The CSR is not shown for simplicity sake.
752
753.. code:: bash
754
755    $ CSR=`cat example.com.csr`
756    $ cli4 --post hostnames='["example.com","*.example.com"]' requested_validity==365 request_type="origin-ecc" csr="$CSR" /certificates
757    {
758        "certificate": "-----BEGIN CERTIFICATE-----\n ... ",
759        "csr": "-----BEGIN CERTIFICATE REQUEST-----\n ... ",
760        "expires_on": "2018-09-27 21:47:00 +0000 UTC",
761        "hostnames": [
762            "*.example.com",
763            "example.com"
764        ],
765        "id": "123456789012345678901234567890123456789012345678",
766        "request_type": "origin-ecc",
767        "requested_validity": 365
768    }
769    $
770
771Deleting a certificate can be done with a **DELETE** call.
772
773.. code:: bash
774
775    $ cli4 --delete /certificates/:123456789012345678901234567890123456789012345678
776    {
777        "id": "123456789012345678901234567890123456789012345678",
778        "revoked_at": "0000-00-00T00:00:00Z"
779    }
780    $
781
782Paging CLI examples
783~~~~~~~~~~~~~~~~~~~
784
785The **--raw** command provides access to the paging returned values. See
786the API documentation for all the info. Here's an example of how to page
787thru a list of zones (it's included in the examples folder as
788**example\_paging\_thru\_zones.sh**).
789
790.. code:: bash
791
792    :
793    tmp=/tmp/$$_
794    trap "rm ${tmp}; exit 0" 0 1 2 15
795    PAGE=0
796    while true
797    do
798        cli4 --raw per_page=5 page=${PAGE} /zones > ${tmp}
799        domains=`jq -c '.|.result|.[]|.name' < ${tmp} | tr -d '"'`
800        result_info=`jq -c '.|.result_info' < ${tmp}`
801        COUNT=`      echo "${result_info}" | jq .count`
802        PAGE=`       echo "${result_info}" | jq .page`
803        PER_PAGE=`   echo "${result_info}" | jq .per_page`
804        TOTAL_COUNT=`echo "${result_info}" | jq .total_count`
805        TOTAL_PAGES=`echo "${result_info}" | jq .total_pages`
806        echo COUNT=${COUNT} PAGE=${PAGE} PER_PAGE=${PER_PAGE} TOTAL_COUNT=${TOTAL_COUNT} TOTAL_PAGES=${TOTAL_PAGES} -- ${domains}
807        if [ "${PAGE}" == "${TOTAL_PAGES}" ]
808        then
809            ## last section
810            break
811        fi
812        # grab the next page
813        PAGE=`expr ${PAGE} + 1`
814    done
815
816It produces the following results.
817
818::
819
820    COUNT=5 PAGE=1 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 -- accumsan.example auctor.example consectetur.example dapibus.example elementum.example
821    COUNT=5 PAGE=2 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 -- felis.example iaculis.example ipsum.example justo.example lacus.example
822    COUNT=5 PAGE=3 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 -- lectus.example lobortis.example maximus.example morbi.example pharetra.example
823    COUNT=5 PAGE=4 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 -- porttitor.example potenti.example pretium.example purus.example quisque.example
824    COUNT=5 PAGE=5 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 -- sagittis.example semper.example sollicitudin.example suspendisse.example tortor.example
825    COUNT=1 PAGE=7 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 -- varius.example vehicula.example velit.example velit.example vitae.example
826    COUNT=5 PAGE=6 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 -- vivamus.example
827
828Paging thru lists (using cursors)
829~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
830
831Some API calls use cursors to read beyond the initally returned values.
832See the API page in order to see which API calls do this.
833
834::
835
836    $ ACCOUNT_ID="00000000000000000000000000000000"
837    $ LIST_ID="00000000000000000000000000000000"
838    $
839    $ cli4 --raw /accounts/::${ACCOUNT_ID}/rules/lists/::${LIST_ID}/items > /tmp/page1.json
840    $ after=`jq -r '.result_info.cursors.after' < /tmp/page1.json`
841    $ echo "after=$after"
842    after=Mxm4GVmKjYbFjy2VxMPipnJigm1M_s6lCS9ABR9wx-RM2A
843    $
844
845Once we have the ``after`` value, we can pass it along in order to read
846the next hunk of values. We finish when ``after`` returns as null (or
847isn't present).
848
849::
850
851    $ cli4 --raw cursor="$after" /accounts/::${ACCOUNT_ID}/rules/lists/::${LIST_ID}/items > /tmp/page2.json
852    $ after=`jq -r '.result_info.cursors.after' < /tmp/page2.json`
853    $ echo "after=$after"
854    after=null
855    $
856
857We can see the results now in two files.
858
859::
860
861    $ jq -c '.result[]' < /tmp/page1.json | wc -l
862          25
863    $
864
865    $ jq -c '.result[]' < /tmp/page2.json | wc -l
866           5
867    $
868
869    $ for f in /tmp/page?.json ; do jq -r '.result[]|.id,.ip,.comment' < $f | paste - - - ; done | column -s'   ' -t
870    0fe44928258549feb47126a966fbf4a0  0.0.0.0           all zero
871    2e1e02120f5e466f8c0e26375e4cf4c8  1.0.0.1           Cloudflare DNS a
872    9ca5fd0ac6f54fdbb9dedd3fb72ce2da  1.1.1.1           Cloudflare DNS b
873    b3654987446743738c782f36ebe074f5  10.0.0.0/8        RFC1918 space
874    90bec8ce37d242faa2e27d1e78c1d8e2  103.21.244.0/22   Cloudflare IP
875    970a3c810cda41af9bef2c36a1892f7e  103.22.200.0/22   Cloudflare IP
876    3ec8516158bf4f3cac18210f611ee541  103.31.4.0/22     Cloudflare IP
877    ee9d268367204e6bb8e5e4c907f22de8  104.16.0.0/12     Cloudflare IP
878    93ae02eda9774c45840af367a02fe529  108.162.192.0/18  Cloudflare IP
879    62891ebf6db44aa494d79a6401af185e  131.0.72.0/22     Cloudflare IP
880    cac40cd940cc470582b8c912a8a12bea  141.101.64.0/18   Cloudflare IP
881    f6d5eacd81a2407f8e0d81caee21e7f8  162.158.0.0/15    Cloudflare IP
882    3d538dfc38ab471d9d3fe78332acfa4e  172.16.0.0/12     RFC1918 space
883    f353cb8f98424837ad35382a22b9debe  172.64.0.0/13     Cloudflare IP
884    78f3e1a0bafc41f88d4d40ad49a642e0  173.245.48.0/20   Cloudflare IP
885    c23a545475c54c32a7681c6b508d3e80  188.114.96.0/20   Cloudflare IP
886    f693237c9e294fe481221cbc2d7c20ef  190.93.240.0/20   Cloudflare IP
887    6d465ab3a0994c07827ebdcf8f34d977  192.168.0.0/16    RFC1918 space
888    1ad1e634b3664bac939086185c62faf7  197.234.240.0/22  Cloudflare IP
889    5d2968e7b3114d8e869a379d71c8ba86  198.41.128.0/17   Cloudflare IP
890    6a69de60b31448fa864f0a3ac5abe8d0  224.0.0.0/24      Multicast
891    30749cce89af4ab3a80e308294f46a46  240.0.0.0/4       Class E
892    2b32c67ea4d044628abe39f28662d8f0  255.255.255.255   all ones
893    cc7cd828b2fb4bcfb9391c2d3ef8d068  2400:cb00::/32    Cloudflare IP
894    b30d4cbd7dcd48729e8ebeda552e48a8  2405:8100::/32    Cloudflare IP
895    49db60758c8344959c338a74afc9748a  2405:b500::/32    Cloudflare IP
896    96e9eca1923c40d5a84865145f5a5d6a  2606:4700::/32    Cloudflare IP
897    21bc52a26e10405d89b7180ddcf49302  2803:f800::/32    Cloudflare IP
898    ff78f842188e4b869eb5389ae9ab8f41  2a06:98c0::/29    Cloudflare IP
899    0880cdfc40b14f6fa0639522a728859d  2c0f:f248::/32    Cloudflare IP
900    $
901
902The ``result_info.cursors`` area also contains a ``before`` value for
903reverse scrolling.
904
905As with ``per_page`` scrolling, raw mode is used.
906
907DNSSEC CLI examples
908~~~~~~~~~~~~~~~~~~~
909
910.. code:: bash
911
912    $ cli4 /zones/:example.com/dnssec | jq -c '{"status":.status}'
913    {"status":"disabled"}
914    $
915
916    $ cli4 --patch status=active /zones/:example.com/dnssec | jq -c '{"status":.status}'
917    {"status":"pending"}
918    $
919
920    $ cli4 /zones/:example.com/dnssec
921    {
922        "algorithm": "13",
923        "digest": "41600621c65065b09230ebc9556ced937eb7fd86e31635d0025326ccf09a7194",
924        "digest_algorithm": "SHA256",
925        "digest_type": "2",
926        "ds": "example.com. 3600 IN DS 2371 13 2 41600621c65065b09230ebc9556ced937eb7fd86e31635d0025326ccf09a7194",
927        "flags": 257,
928        "key_tag": 2371,
929        "key_type": "ECDSAP256SHA256",
930        "modified_on": "2016-05-01T22:42:15.591158Z",
931        "public_key": "mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==",
932        "status": "pending"
933    }
934    $
935
936Zone file upload and download CLI examples (uses BIND format files)
937~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
938
939Refer to `Import DNS
940records <https://api.cloudflare.com/#dns-records-for-a-zone-import-dns-records>`__
941on API documentation for this feature.
942
943.. code:: bash
944
945    $ cat zone.txt
946    example.com.            IN      SOA     somewhere.example.com. someone.example.com. (
947                                    2017010101
948                                    3H
949                                    15
950                                    1w
951                                    3h
952                            )
953
954    record1.example.com.    IN      A       10.0.0.1
955    record2.example.com.    IN      AAAA    2001:d8b::2
956    record3.example.com.    IN      CNAME   record1.example.com.
957    record4.example.com.    IN      TXT     "some text"
958    $
959
960    $ cli4 --post file=@zone.txt /zones/:example.com/dns_records/import
961    {
962        "recs_added": 4,
963        "total_records_parsed": 4
964    }
965    $
966
967The following is documented within the **Advanced** option of the DNS
968page within the Cloudflare portal.
969
970::
971
972    $ cli4 /zones/:example.com/dns_records/export | egrep -v '^;;|^$'
973    $ORIGIN .
974    @   3600    IN  SOA example.com.    root.example.com.   (
975            2025552311  ; serial
976            7200        ; refresh
977            3600        ; retry
978            86400       ; expire
979            3600)       ; minimum
980    example.com.    300 IN  NS  REPLACE&ME$WITH^YOUR@NAMESERVER.
981    record4.example.com.    300 IN  TXT "some text"
982    record3.example.com.    300 IN  CNAME   record1.example.com.
983    record1.example.com.    300 IN  A   10.0.0.1
984    record2.example.com.    300 IN  AAAA    2001:d8b::2
985    $
986
987The egrep is used for documentation brevity.
988
989This can also be done via Python code with the following example.
990
991::
992
993    #!/usr/bin/env python
994    import sys
995    import CloudFlare
996
997    def main():
998        zone_name = sys.argv[1]
999        cf = CloudFlare.CloudFlare()
1000
1001        zones = cf.zones.get(params={'name': zone_name})
1002        zone_id = zones[0]['id']
1003
1004        dns_records = cf.zones.dns_records.export.get(zone_id)
1005        for l in dns_records.splitlines():
1006            if len(l) == 0 or l[0] == ';':
1007                continue
1008            print l
1009        exit(0)
1010
1011    if __name__ == '__main__':
1012        main()
1013
1014Cloudflare Workers
1015~~~~~~~~~~~~~~~~~~
1016
1017Cloudflare Workers are described on the Cloudflare blog at
1018`here <https://blog.cloudflare.com/introducing-cloudflare-workers/>`__
1019and
1020`here <https://blog.cloudflare.com/code-everywhere-cloudflare-workers/>`__,
1021with the beta release announced
1022`here <https://blog.cloudflare.com/cloudflare-workers-is-now-on-open-beta/>`__.
1023
1024The Python libraries now support the Cloudflare Workers API calls. The
1025following javascript is lifted from https://cloudflareworkers.com/ and
1026slightly modified.
1027
1028::
1029
1030    $ cat modify-body.js
1031    addEventListener("fetch", event => {
1032      event.respondWith(fetchAndModify(event.request));
1033    });
1034
1035    async function fetchAndModify(request) {
1036      console.log("got a request:", request);
1037
1038      // Send the request on to the origin server.
1039      const response = await fetch(request);
1040
1041      // Read response body.
1042      const text = await response.text();
1043
1044      // Modify it.
1045      const modified = text.replace(
1046      "<body>",
1047      "<body style=\"background: #ff0;\">");
1048
1049      // Return modified response.
1050      return new Response(modified, {
1051        status: response.status,
1052        statusText: response.statusText,
1053        headers: response.headers
1054      });
1055    }
1056    $
1057
1058Here's the website with it's simple ``<body>`` statement
1059
1060::
1061
1062    $ curl -sS https://example.com/ | fgrep '<body'
1063      <body>
1064    $
1065
1066Now lets add the script. Looking above, you will see that it's simple
1067action is to modify the ``<body>`` statement and make the background
1068yellow.
1069
1070::
1071
1072    $ cli4 --put @- /zones/:example.com/workers/script < modify-body.js
1073    {
1074        "etag": "1234567890123456789012345678901234567890123456789012345678901234",
1075        "id": "example-com",
1076        "modified_on": "2018-02-15T00:00:00.000000Z",
1077        "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",
1078        "size": 603
1079    }
1080    $
1081
1082The following call checks that the script is associated with the zone.
1083In this case, it's the only script added by this user.
1084
1085::
1086
1087    $ cli4 /user/workers/scripts
1088    [
1089        {
1090            "created_on": "2018-02-15T00:00:00.000000Z",
1091            "etag": "1234567890123456789012345678901234567890123456789012345678901234",
1092            "id": "example-com",
1093            "modified_on": "2018-02-15T00:00:00.000000Z"
1094        }
1095    ]
1096    $
1097
1098Next step is to make sure a route is added for that script on that zone.
1099
1100::
1101
1102    $ cli4 --post pattern="example.com/*" script="example-com" /zones/:example.com/workers/routes
1103    {
1104        "id": "12345678901234567890123456789012"
1105    }
1106    $
1107
1108    $ cli4 /zones/:example.com/workers/routes
1109    [
1110        {
1111            "id": "12345678901234567890123456789012",
1112            "pattern": "example.com/*",
1113            "script": "example-com"
1114        }
1115    ]
1116    $
1117
1118With that script added to the zone and the route added, we can now see
1119the website has been modified because of the Cloudflare Worker.
1120
1121::
1122
1123    $ curl -sS https://example.com/ | fgrep '<body'
1124      <body style="background: #ff0;">
1125    $
1126
1127All this can be removed; hence bringing the website back to its initial
1128state.
1129
1130::
1131
1132    $ cli4 --delete /zones/:example.com/workers/script
1133    12345678901234567890123456789012
1134    $ cli4 --delete /zones/:example.com/workers/routes/:12345678901234567890123456789012
1135    true
1136    $
1137
1138    $ curl -sS https://example.com/ | fgrep '<body'
1139      <body>
1140    $
1141
1142Refer to the Cloudflare Workers API documentation for more information.
1143
1144Cloudflare GraphQL
1145------------------
1146
1147The GraphQL interface can be accessed via the command line or via
1148Python.
1149
1150::
1151
1152        query="""
1153          query {
1154            viewer {
1155                zones(filter: {zoneTag: "%s"} ) {
1156                httpRequests1dGroups(limit:40, filter:{date_lt: "%s", date_gt: "%s"}) {
1157                  sum { countryMap { bytes, requests, clientCountryName } }
1158                  dimensions { date }
1159                }
1160              }
1161            }
1162          }
1163        """ % (zone_id, date_before[0:10], date_after[0:10])
1164
1165        r = cf.graphql.post(data={'query':query})
1166
1167        httpRequests1dGroups = zone_info = r['data']['viewer']['zones'][0]['httpRequests1dGroups']
1168
1169See the `examples/example\_graphql.sh <examples/example_graphql.sh>`__
1170and `examples/example\_graphql.py <examples/example_graphql.py>`__ files
1171for working examples. Here is the working example of the shell version:
1172
1173::
1174
1175    $ examples/example_graphql.sh example.com
1176    2020-07-14T02:00:00Z    34880
1177    2020-07-14T03:00:00Z    18953
1178    2020-07-14T04:00:00Z    28700
1179    2020-07-14T05:00:00Z    2358
1180    2020-07-14T06:00:00Z    34905
1181    2020-07-14T07:00:00Z    779
1182    2020-07-14T08:00:00Z    35450
1183    2020-07-14T10:00:00Z    17803
1184    2020-07-14T11:00:00Z    32678
1185    2020-07-14T12:00:00Z    19947
1186    2020-07-14T13:00:00Z    4956
1187    2020-07-14T14:00:00Z    34585
1188    2020-07-14T15:00:00Z    3022
1189    2020-07-14T16:00:00Z    5224
1190    2020-07-14T18:00:00Z    79482
1191    2020-07-14T21:00:00Z    10609
1192    2020-07-14T22:00:00Z    5740
1193    2020-07-14T23:00:00Z    2545
1194    2020-07-15T01:00:00Z    10777
1195    $
1196
1197For more information on how to use GraphQL at Cloudflare, refer to the
1198`Cloudflare GraphQL Analytics
1199API <https://developers.cloudflare.com/analytics/graphql-api>`__. It
1200contains a full overview of Cloudflare's GraphQL features and keywords.
1201
1202Implemented API calls
1203---------------------
1204
1205The **--dump** argument to cli4 will produce a list of all the call
1206implemented within the library.
1207
1208.. code:: bash
1209
1210    $ cli4 --dump
1211    /certificates
1212    /ips
1213    /organizations
1214    ...
1215    /zones/ssl/analyze
1216    /zones/ssl/certificate_packs
1217    /zones/ssl/verification
1218    $
1219
1220Table of commands
1221~~~~~~~~~~~~~~~~~
1222
1223+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1224| ``GET``   | ``PUT``   | ``POST``   | ``PATCH``   | ``DELETE``   | API call                                                      |
1225+===========+===========+============+=============+==============+===============================================================+
1226| ``GET``   |           | ``POST``   |             | ``DELETE``   | /certificates                                                 |
1227+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1228| ``GET``   |           |            |             |              | /ips                                                          |
1229+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1230| ``GET``   |           |            | ``PATCH``   |              | /organizations                                                |
1231+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1232| ``GET``   |           | ``POST``   | ``PATCH``   | ``DELETE``   | /organizations/:identifier/firewall/access\_rules/rules       |
1233+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1234|           |           |            | ``PATCH``   |              | /organizations/:identifier/invite                             |
1235+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1236| ``GET``   |           | ``POST``   |             | ``DELETE``   | /organizations/:identifier/invites                            |
1237+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1238| ``GET``   |           |            | ``PATCH``   | ``DELETE``   | /organizations/:identifier/members                            |
1239+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1240| ``GET``   |           | ``POST``   | ``PATCH``   | ``DELETE``   | /organizations/:identifier/railguns                           |
1241+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1242| ``GET``   |           |            |             |              | /organizations/:identifier/railguns/:identifier/zones         |
1243+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1244| ``GET``   |           |            |             |              | /organizations/:identifier/roles                              |
1245+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1246| ``GET``   |           | ``POST``   | ``PATCH``   | ``DELETE``   | /organizations/:identifier/virtual\_dns                       |
1247+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1248| ``GET``   |           | ``POST``   | ``PATCH``   | ``DELETE``   | /railguns                                                     |
1249+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1250| ``GET``   |           |            |             |              | /railguns/:identifier/zones                                   |
1251+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1252| ``GET``   |           |            | ``PATCH``   |              | /user                                                         |
1253+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1254| ``GET``   |           |            |             |              | /user/billing/history                                         |
1255+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1256| ``GET``   |           |            |             |              | /user/billing/profile                                         |
1257+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1258| ``GET``   |           |            |             |              | /user/billing/subscriptions/apps                              |
1259+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1260| ``GET``   |           |            |             |              | /user/billing/subscriptions/zones                             |
1261+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1262| ``GET``   |           | ``POST``   | ``PATCH``   | ``DELETE``   | /user/firewall/access\_rules/rules                            |
1263+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1264| ``GET``   |           |            | ``PATCH``   |              | /user/invites                                                 |
1265+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1266| ``GET``   |           |            |             | ``DELETE``   | /user/organizations                                           |
1267+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1268| ``GET``   |           | ``POST``   | ``PATCH``   | ``DELETE``   | /user/virtual\_dns                                            |
1269+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1270| ``GET``   |           | ``POST``   | ``PATCH``   | ``DELETE``   | /zones                                                        |
1271+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1272|           | ``PUT``   |            |             |              | /zones/:identifier/activation\_check                          |
1273+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1274| ``GET``   |           |            |             |              | /zones/:identifier/analytics/colos                            |
1275+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1276| ``GET``   |           |            |             |              | /zones/:identifier/analytics/dashboard                        |
1277+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1278| ``GET``   |           |            |             |              | /zones/:identifier/available\_plans                           |
1279+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1280|           | ``PUT``   |            |             |              | /zones/:identifier/custom\_certificates/prioritize            |
1281+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1282| ``GET``   |           | ``POST``   | ``PATCH``   | ``DELETE``   | /zones/:identifier/custom\_certificates                       |
1283+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1284| ``GET``   | ``PUT``   |            |             |              | /zones/:identifier/custom\_pages                              |
1285+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1286| ``GET``   | ``PUT``   | ``POST``   |             | ``DELETE``   | /zones/:identifier/dns\_records                               |
1287+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1288| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/firewall/waf/packages/:identifier/groups   |
1289+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1290| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/firewall/waf/packages/:identifier/rules    |
1291+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1292| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/firewall/waf/packages                      |
1293+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1294| ``GET``   |           | ``POST``   | ``PATCH``   | ``DELETE``   | /zones/:identifier/firewall/access\_rules/rules               |
1295+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1296| ``GET``   |           | ``POST``   | ``PATCH``   | ``DELETE``   | /zones/:identifier/keyless\_certificates                      |
1297+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1298| ``GET``   | ``PUT``   | ``POST``   | ``PATCH``   | ``DELETE``   | /zones/:identifier/pagerules                                  |
1299+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1300|           |           |            |             | ``DELETE``   | /zones/:identifier/purge\_cache                               |
1301+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1302| ``GET``   |           |            |             |              | /zones/:identifier/railguns/:identifier/diagnose              |
1303+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1304| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/railguns                                   |
1305+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1306| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings                                   |
1307+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1308| ``GET``   |           |            |             |              | /zones/:identifier/settings/advanced\_ddos                    |
1309+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1310| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/always\_online                    |
1311+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1312| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/browser\_cache\_ttl               |
1313+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1314| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/browser\_check                    |
1315+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1316| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/cache\_level                      |
1317+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1318| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/challenge\_ttl                    |
1319+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1320| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/development\_mode                 |
1321+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1322| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/email\_obfuscation                |
1323+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1324| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/hotlink\_protection               |
1325+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1326| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/ip\_geolocation                   |
1327+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1328| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/ipv6                              |
1329+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1330| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/minify                            |
1331+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1332| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/mirage                            |
1333+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1334| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/mobile\_redirect                  |
1335+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1336| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/origin\_error\_page\_pass\_thru   |
1337+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1338| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/polish                            |
1339+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1340| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/prefetch\_preload                 |
1341+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1342| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/response\_buffering               |
1343+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1344| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/rocket\_loader                    |
1345+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1346| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/security\_header                  |
1347+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1348| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/security\_level                   |
1349+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1350| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/server\_side\_exclude             |
1351+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1352| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/sort\_query\_string\_for\_cache   |
1353+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1354| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/ssl                               |
1355+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1356| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/tls\_1\_2\_only                   |
1357+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1358| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/tls\_client\_auth                 |
1359+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1360| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/true\_client\_ip\_header          |
1361+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1362| ``GET``   |           |            | ``PATCH``   |              | /zones/:identifier/settings/waf                               |
1363+-----------+-----------+------------+-------------+--------------+---------------------------------------------------------------+
1364
1365Adding extra API calls manually
1366-------------------------------
1367
1368Extra API calls can be added via the configuration file
1369
1370.. code:: bash
1371
1372    $ cat ~/.cloudflare/cloudflare.cfg
1373    [CloudFlare]
1374    extras =
1375        /client/v4/command
1376        /client/v4/command/:command_identifier
1377        /client/v4/command/:command_identifier/settings
1378    $
1379
1380While it's easy to call anything within Cloudflare's API, it's not very
1381useful to add items in here as they will simply return API URL errors.
1382Technically, this is only useful for internal testing within Cloudflare.
1383
1384Issues
1385------
1386
1387The following error can be caused by an out of date SSL/TLS library
1388and/or out of date Python.
1389
1390::
1391
1392    /usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/util/ssl_.py:318: SNIMissingWarning: An HTTPS request has been made, but the SNI (Subject Name Indication) extension to TLS is not available on this platform. This may cause the server to present an incorrect TLS certificate, which can cause validation failures. You can upgrade to a newer version of Python to solve this. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#snimissingwarning.
1393      SNIMissingWarning
1394    /usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/util/ssl_.py:122: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. You can upgrade to a newer version of Python to solve this. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
1395      InsecurePlatformWarning
1396
1397The solution can be found
1398`here <https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning>`__
1399and/or
1400`here <http://stackoverflow.com/questions/35144550/how-to-install-cryptography-on-ubuntu>`__.
1401
1402Python 2.x vs 3.x support
1403-------------------------
1404
1405As of May/June 2016 the code is now tested against pylint. This was
1406required in order to move the codebase into Python 3.x. The motivation
1407for this came from `Danielle Madeley
1408(danni) <https://github.com/danni>`__.
1409
1410[STRIKEOUT:While the codebase has been edited to run on Python 3.x,
1411there's not been enough Python 3.x testing performed.] [STRIKEOUT:If you
1412can help in this regard; please contact the maintainers.]
1413
1414As of January 2020 the code is Python3 clean.
1415
1416As of January 2020 the code is shipped up to pypi with Python2 support
1417removed.
1418
1419As of January 2020 the code is Python3.8 clean. The new
1420``SyntaxWarning`` messages (i.e.
1421``SyntaxWarning: "is" with a literal. Did you mean "=="?``) meant minor
1422edits were needed.
1423
1424Credit
1425------
1426
1427This is based on work by `Felix Wong
1428(gnowxilef) <https://github.com/gnowxilef>`__ found
1429`here <https://github.com/cloudflare-api/python-cloudflare-v4>`__. It
1430has been seriously expanded upon.
1431
1432Changelog
1433---------
1434
1435An automatically generated CHANGELOG is provided
1436`here <CHANGELOG.md>`__.
1437
1438Copyright
1439---------
1440
1441Portions copyright `Felix Wong
1442(gnowxilef) <https://github.com/gnowxilef>`__ 2015 and Cloudflare 2016.
1443