A thin Python wrapper for the City of Philadelphia L&I API
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.



A thin Python wrapper for the City of Philadelphia Licenses and Inspections API.


pip install py-li


The L&I API allows you to get details about L&I documents on a document by document basis, or in bulk. It's worth looking over the documentation to see what document types and document details are available before diving in. Also, the API is based on Version 2.0 of the ODATA API Protocol. The documentation is available here.

To get started, import the py-li library:

>>> import li

The API is accessed through li by two types of methods: those that get multiple documents, and those that get a single document. Methods that retrieve multiple documents are plural, i.e. get_permits, get_violations and methods that retrieve a single document are singular, i.e. get_permit, get_violation.

Multiple Documents

The methods that return multiple documents accept the following parameters:

At the most basic level, you can easily retrieve permits (at the moment, a single bulk call to the API is limited to 1,000 documents), for instance, by doing the following:

>>> response = li.get_permits()
>>> response['results']
[{u'status': u'COMPLETED', u'updated_datetime': u'/Date(1259076435000)/', u'application_type': u'BP_ALTER', u'locations': 
{u'__deferred': {u'uri': u"http://services.phila.gov/PhillyAPI/Data/v0.7/Service.svc/permits('101930')/locations"}},
u'issued_datetime': u'/Date(1194015498000)/', u'buildingboardappeals': {u'__deferred': {u'uri': u"http://services.phila
gov/PhillyAPI/Data/v0.7/Service.svc/permits('101930')/buildingboardappeals"}}, u'pri_contact_first_name': u' ',
u'pri_contact_address1': u'2023 W GIRARD AVE', u'pri_contact_address2': None, u'pri_contact_zip': u'19130', u'location_id': 71707,

Get the 1,000 most recent permits

>>> response = li.get_permits(orderby='issued_datetime desc')

Passing count=True, which is a more convienent way to specify inlinecount=allpages query parameter, returns the total number of documents of the requested type that are available through the API:

>>> response = li.get_permits(count=True)
>>> len(response[results]) # number of results returned
>>> response['count'] # total number of permits available through API

The top query parameter limits the number of results returned:

>>> response = li.get_licenses(top=10)
>>> len(response['results'])

To receive a quick count of the total number of requested documents, use the top query parameter and count=True:

>>> response = li.get_licenses(top=0, count=True)
>>> len(response['results'])
>>> response['count']

Pass a ODATA SQL query as the filter parameter to get all the permits issued in 2013, for instance:

>>> sql = "issued_datetime gt DateTime'2013-01-01'"
>>> response = li.get_permits(filter=sql)

Get a count of the number of CLIP violations issued in 2013:

>>> sql = "violation_code eq 'CP-01' and violation_datetime gt DateTime'2013-01-01'"
>>> response = li.get_violations(count=True, top=0, filter=sql)
>>> response['count']

If you make a bad request, the error message from the API is passed along and stored at response['error']:

>>> sql = "just new stuff"
>>> response = li.get_licenses(filter=sql)
>>> len(response['results'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'NoneType' has no len()
>>> response['error']
{u'message': {u'lang': u'en-US', u'value': u"No property 'just' exists in type 'Philagov.Data.PlanPhillyModel.licenses' at position 0."}, u'code': u''}}

Single Document

Once you've retrieved a set of documents, you have the information you need to start getting more details on a single document.

>>> response = li.get_permits(orderby='issued_datetime desc')
>>> response['results'][0]['permit_number']
>>> response = li.get_permit('464771', related=True)
>>> response['results']
{u'status': u'ACTIVE', u'updated_datetime': u'/Date(1370290469000)/', u'application_type': u'BP_NEWCNST', u'locations': 
{u'council_district': u'04', u'zoningboardappeals': {u'__deferred': {u'uri': u'http://services.phila.gov/PhillyApi/Data/v0.
7/Service.svc/locations(758962)/zoningboardappeals'}}, u'street_name': u'DRIFTWOOD', u'census_tract': u'027', 
u'violationdetails': {u'__deferred': {u'uri': u'http://services.phila.gov/PhillyApi/Data/v0.7/Service.svc/locations(758962)
/violationdetails'}}, u'licenses': {u'__deferred': {u'uri': u'http://services.phila.gov/PhillyApi/Data/v0.7/Service.svc/locations(
758962)/licenses'}}, u'condo_unit': u'128', u'location_id': 758962, u'cases': {u'__deferred': {u'uri': u'http://services.phila.
gov/PhillyApi/Data/v0.7/Service.svc/locations(758962)/cases'}}, u'city': u'PHILADELPHIA', u'zip': u'19129-1733',

By passing related=True in the above example, we tell li to retrieve the related documents for the requested document. The related documents for a permit are locations, zoningboardappeals, and buildingboardappeals. Without specifying related=True, the API returns does not get the details of the related documents, but instead returns a URL to get the details. For example:

    'pri_contact_type': 'APPLICANT',
    'zoningboardappeals': {
    '__deferred': {
        'uri': u"http://services.phila.gov/PhillyAPI/Data/v0.7/Service.svc/permits('464771')/zoningboardappeals"
    'permit_type_code': 'ENTIRE',

Methods that return a single document accept the following parameters:

  • doc_id: String. The unique ID of the document you want to retrieve
  • related: Boolean. If true, retrieves all documents related to the requested document.

See the examples folder and test.py for more usage examples.


Install the library:

  1. Fork it

  2. Install it:

     cd py-li
     mkvirtualenv venv
     source venv/bin/activate
     pip install -r requirements.txt
     python test.py
  3. Create your feature branch (git checkout -b my-new-feature)

  4. Commit your changes (git commit -am 'Added some feature')

  5. Push to the branch (git push origin my-new-feature)

  6. Create new pull Request

Make sure you added test cases for your feature!


Copyright © AxisPhilly. See LICENSE for details.