Official PHP driver for the Factual API
Latest commit 8b44b49 Apr 11, 2016 @dirtyvagabond dirtyvagabond Merge pull request #34 from samdark/patch-1
Fixed handling of deprecated IDs
Failed to load latest commit information.
oauth-php exposed curlinfo outside class for debug Oct 31, 2012
wordpress updated mapbox reg details Aug 12, 2015
.gitignore updated Apr 1, 2013
BareResponse.php Added header-only response object Oct 28, 2013 Updated for 1.5.4 release Oct 17, 2013
DiffsQuery.php Added Diffs Jul 25, 2012
DiffsResponse.php minor tweaking in diffs stat generation Dec 9, 2012
FacetQuery.php ReadResponse and descendants now preserve index of data Aug 13, 2012
Factual.php Fixed handling of deprecated IDs Apr 11, 2016
FactualApiException.php Assign error code to object on instantiation Oct 17, 2013
FactualBoost.php Added boost Oct 28, 2013
FactualCircle.php Added parameter check on instantiation Mar 15, 2012
FactualClearor.php Restructured postvars Jan 30, 2013
FactualColumnSchema.php - Added a few quick-and-dirty integration/unit test Jan 27, 2012
FactualFilter.php - Added a few quick-and-dirty integration/unit test Jan 27, 2012
FactualFlagger.php Added "relocated" as valid flag Jun 23, 2014
FactualPlace.php - Added a few quick-and-dirty integration/unit test Jan 27, 2012
FactualPoint.php Added Factual Reverse Geocoding Jul 9, 2012
FactualPost.php Moved setValue() to parent class, from FactualSubmit to FactualPost Oct 28, 2013
FactualQuery.php support for threshold Jul 14, 2014
FactualRectangle.php initial commit Jan 22, 2014
FactualResponse.php accommodating delyed writes in submit response Nov 28, 2014
FactualSubmittor.php Fixed class scope and egregious array issue in getPostVars() Jan 22, 2014
FactualTest.php $res isn't set it exception is thrown in fetch Mar 10, 2014
FieldFilter.php Change FieldFilter() to __construct for consistency Apr 3, 2012
FilterGroup.php - Added a few quick-and-dirty integration/unit test Jan 27, 2012
FlagResponse.php Updated documentation Jul 29, 2012
GeocoderWrapper.php Added debug method to geocoder Oct 2, 2012
LICENSE.txt Initial Commit Jan 23, 2012
MatchQuery.php Updated Match and Resolve to latest API release Jul 29, 2012
MatchResponse.php Updated Match and Resolve to latest API release Jul 29, 2012
MultiResponse.php MultiResponse now inherits from FactualResponse. Jul 25, 2012
QueryBuilder.php added includes(), includesAny() Apr 1, 2013 removed reference to geopulse Dec 2, 2015
ReadResponse.php fixed bug in getDataAsJSON() May 2, 2013
ResolveQuery.php Update ResolveQuery.php Dec 2, 2015
ResolveResponse.php Updated Match and Resolve to latest API release Jul 29, 2012
SchemaResponse.php Normalized response classes. Now inherit uniformly from FactualResponse Jul 24, 2012
SubmitResponse.php Add check on array key to prevent "undefined index" error Dec 10, 2014
composer.json Update composer.json Nov 9, 2012
config.ini Changed version to 1.5.8 Jun 23, 2014
test.php script now takes key and secret as parameters on cmdln Nov 1, 2012


This is the official PHP driver for the Factual API. It is crafted with artisanal skill from native hardwoods.

The driver allows you to create an authenticated handle to Factual. With a Factual handle, you can send queries and get results back as PHP Arrays and Objects.

Many Bothamns died to bring you this documentation.

PHP Specifics


  • PHP >=5.1.2 is required.
  • The php5-curl module is required.
  • SPL is required for autoloading.
  • JSON is required. Some distributions as of PHP 5.5rc2 lack the previously included JSON extension due to a license conflict. Use sudo apt-get install php5-json.

The package includes lightly modified Google's oauth libraries


All classes are autoloaded. Just require_once("Factual.php") and you're set.

The PHP __autoload() method is deprecated; this library uses spl_autoload_register(). The Factual Autoload will not mess with other libraries or frameworks.

Getting Started

Get an Oauth Key & Secret

Obtain an oauth key and secret from Factual at Do not expose your secret to third-parties or distribute it in PHP code (reminder: that's why it is called 'secret').

Test Your Integration and Environment

Run test.php on the command line:

'php test.php yourFactualKey yourFactualSecret [logfile]'

On windows remember to use the -f switch:

'php -f test.php yourFactualKey yourFactualSecret [logfile]'  

This checks your PHP install environment and performs a number of unit tests. The script takes your key as parameter one, your secret is parameter two, and an optional output file as parameter three. By default it echoes to stdout.

Using the Driver

Require the file 'Factual.php, and instantiate a factual object with the key and secret as parameters'

    $factual = new Factual("yourOauthKey","yourOauthSecret");

The driver creates an authenticated handle to Factual and configures class loading on instantiation, so be sure to always instantiate a Factual object first.

Simple Query Example

(Remember, first create a Factual object as we've done above.)

    // Find 3 random records 
    $query = new FactualQuery;
    $res = $factual->fetch("places", $query);

Full Text Search Example

    // Find entities that match a full text search for Sushi in Santa Monica:
    $query = new FactualQuery;
    $query->search("Sushi Santa Monica");
    $res = $factual->fetch("places", $query);

See Working with Query Results for details on iterating through the results of your query, and obtaining query metadata.

Unnecessary Reminder: we use print_r() in these examples so you can review the output visually. Obviously, but worth a reminder nonetheless, you do not want to use print_r() in production.

A boatload of tools are available in the driver to help you understand your request, and what the server is (or is not) returning:

Debugging and Support

Where to Get Help

If you have a question or are having any other kind of issue, such as unexpected data or strange behaviour from Factual's API (or you're just not sure WTF is going on), please hit us up on Factual Support. Be sure to include the debug information, and what driver you are using; help us help you. Provide as much information as you can, including:

  • All of the debug info output by the exception (above)
  • What you did to surface the bug -- specific code with values rather than variables please
  • What you expected to happen & what actually happened
  • Detailed stack trace and/or line numbers

Debug Mode

The Factual object can be switched to debug mode, which will echo (to stderr) the cURL process and any other information we can provide:

    $factual = new Factual($key,$secret);

When debug mode is enabled, cURL status and exceptions are also output to stderr:

cURL Debug Output

* About to connect() to port 80 (#0)
*   Trying * connected
* Connected to ( port 80 (#0)
> POST /t/2EH4Pz/f33527e0-a8b4-4808-a820-2686f18cb00c/submit HTTP/1.1
User-Agent: anyMeta/OAuth 1.0 - ($LastChangedRevision: 174 $)
Accept: */*
X-Factual-Lib: factual-php-driver-v1.4.3
Content-Type: application/x-www-form-urlencoded
Authorization: OAuth realm="", oauth_signature_method="HMAC-SHA1",      
oauth_timestamp="1342996381", oauth_token="", 
Content-Length: 102

< HTTP/1.1 400 Bad Request
< access-control-allow-origin: *
< age: 0
< cache-control: max-age=2592000
< Content-Type: application/json; charset=utf-8
< Date: Sun, 22 Jul 2012 22:33:02 GMT
< Server: nginx/1.0.15
< Content-Length: 196
< Connection: keep-alive
* Connection #0 to host left intact
* Closing connection #0

Exception Debug Output

[code] => 400
[version] => 3
[status] => error
[error_type] => InvalidJsonArgument
[message] => Parameter 'values' contains an error in its JSON syntax.  
             For documentation, please see:
[request] =>
[returnheaders] => Array
        [access-control-allow-origin] => *
        [age] => 0
        [cache-control] => max-age=2592000
        [content-type] => application/json; charset=utf-8
        [date] => Sun, 22 Jul 2012 22:33:02 GMT
        [server] => nginx/1.0.15
        [content-length] => 196
        [connection] => keep-alive

[driver] => factual-php-driver-v1.4.3
[method] => POST
[body] => Array
        [user] => testUser
        [values] => %7B%22factual_id%22%3A%22f33527e0-a8b4-4808-a820-2686f18cb00c%22%7D


Exception Handling

If Factual's API indicates an error, a FactualApiException unchecked Exception will be thrown. Detailed debug information can be studied using debug mode (above) or calling FactualApiException::debug(). Here is an example of catching a FactualApiException and inspecting it:

        $query->field("badFieldName")->notIn("Los Angeles"); //this line borks 
        $res = $factual->fetch("places", $query);
    } catch (FactualApiException $e) {

Call Introspection

A number of Result object methods allow introspection into the call itself. These can be accessed at any time and do not require the Factual object to be set in Debug mode:

Method Function Notes
getCode() Get http status code returned by Factual Useful for re-directs from deprecation
getHeaders() Get http headers returned by Factual
getTable() Get table name queried
getRawRequest() Get url-encoded request string Does not include auth component
getRequest() Get url-decoded request string (As above)
isEmpty() Checks whether data was returned by Factual
size() Gets count of elements returned in this page of result set (not the total count)
getVersion() Get Factual API version
getStatus() Get the status returned by the Factual API server e.g. "OK"
getJSON() Gets entire JSON string returned by Factual


The schema endpoint returns table metadata:

    $res = $factual->schema("places");

Schema API Documentation:


Use the read API call to query data in Factual tables with any combination of full-text search, parametric filtering, and geo-location filtering.

Read API documentation:

Related place-specific documentation:

Read Filters

Use the follow syntax to perform searches against Factual data:

Parameter Description Example
filters Restrict the data returned to conform to specific conditions. For all possible Row Filters, see the eponymous section, below. $query->field("name")->beginsWith("Starbucks")
include count Include a count of the total number of rows in the dataset that conform to the request based on included filters. Requesting the row count will increase the time required to return a response. The default behavior is to NOT include a row count. When the row count is requested, the Response object will contain a valid total row count via .getTotalRowCount(). $query->includeRowCount()
geo Restrict data to be returned to be within a geographical range based. (See the section on Geo Filters)
limit Maximum number of rows to return. Default is 20. The system maximum is 50. For higher limits please contact Factual, however consider requesting a download of the data if your use case is requesting more data in a single query than is required to fulfill a single end-user's request. $query->limit(10)
search Full text search query string. Find "sushi":

Find "sushi" or "sashimi":
$query->search("sushi, sashimi")

Find "sushi" and "santa" and "monica":
$query->search("sushi santa monica")

offset Number of rows to skip before returning a page of data. Maximum value is 500 minus any value provided under limit. Default is 0. $query->offset(150)
select What fields to include in the query results. Note that the order of fields will not necessarily be preserved in the resulting JSON response due to the nature of JSON hashes. $query->only("name,tel,category") or $query->select(array("name","tel","category"))
sort The field (or fields) to sort data on, as well as the direction of sort. Supports $distance as a sort option if a geo-filter is specified. Supports $relevance as a sort option if a full text search is specified either using the q parameter or using the $search operator in the filter parameter. By default, any query with a full text search will be sorted by relevance. Any query with a geo filter will be sorted by distance from the reference point. If both a geo filter and full text search are present, the default will be relevance followed by distance. $query->sortAsc("name")
threshold Set a threshold for filtering on the level of confidence that Factual has that places exist. Valid values are confident, default, or comprehensive. If the threshold parameter is not specified, it uses the value default. $query->threshold("confident");

Extracting Data from Query Results

The drivers parse the JSON for you and return a result object as a result of factual::fetch(), but you can work directly with JSON, Arrays, or Objects. In these examples, $res is the result object returned by an API query:

    //Get the original JSON (includes status and metadata)
    $res = $res->getJson();

    //Get the entities as array of arrays
    $res = $res->getData();

    //Get the entities as a JSON array
    $res = $res->getDataAsJSON();   

    //iterate through the result records, just like an array
    foreach ($res as $entity){
    //your code

To help with debugging, we also provide in the response object metadata about the query and the response. See the section on Call Introspection, above.

Field Selection

By default your queries will return all fields in the table. You can use the only modifier to specify the exact set of fields returned. For example:

    // Build a Query that only gets the name, tel, and category fields:
    $query = new FactualQuery;
    $res = $factual->fetch("places", $query);

Row Filters

The driver supports various row filter logic. See the Row Filter API documentation.


    // Build a query to find places whose name field starts with "Starbucks"
    $query = new FactualQuery;
    $res = $factual->fetch("places", $query);

    // Build a query to find places with a blank telephone number
    $query = new FactualQuery;
    $res = $factual->fetch("places", $query);

Supported Row Filter Logic

Predicate Description Example
equal equal to $query->field("region")->equal("CA")
notEqual not equal to $query->field("region")->notEqual("CA")
search full text search $query->field("name")->search("fried chicken")
in equals any of. Requires array. $query->field("region")->in(array("MA", "VT", "NH", "RI", "CT"))
notIn does not equal any of. Requires array. $query->field("locality")->notIn(array("Los Angeles","Philadelphia")
beginsWith begins with $query->field("name")->beginsWith("b")
notBeginsWith does not begin with $query->field("name")->notBeginsWith("star")
beginsWithAny begins with any of. Requires array. $query->field("name")->beginsWithAny(array("star", "coffee", "tull"))
notBeginsWithAny does not begin with any of. Requires array. $query->field("name")->notBeginsWithAny(array("star", "coffee", "tull"))
blank is blank or null $query->field("tel")->blank()
notBlank is not blank or null $query->field("tel")->notBlank()
greaterThan greater than $query->field("rating")->greaterThan(7.5)
greaterThanOrEqual greater than or equal to $query->field("rating")->greaterThanOrEqual(7.5)
lessThan less than $query->field("rating")->lessThan(7.5)
lessThanOrEqual less than or equal to $query->field("rating")->lessThanOrEqual(7.5)
includes includes this value $query->field("category_ids")->includes(10)
includesAny includes any of these values. Requires array. $query->field("cuisine")->includesAny(array("sushi","bistro"))


Queries support logical AND'ing your row filters. For example:

    // Build a query to find entities where the name begins with "Coffee" AND the telephone is blank:
    $query = new FactualQuery;
    $res = $factual->fetch("places", $query);

Note that all row filters set at the top level of the Query are implicitly AND'ed together, so you could also do this:

    //Combined query alternative syntax
    $query = new FactualQuery;
    $res = $factual->fetch("places", $query);


Queries support logical OR'ing your row filters. For example:

    // Build a query to find entities where the name begins with "Coffee" OR the telephone is blank:
    $query = new FactualQuery;
    $res = $factual->fetch("places", $query);

Combined ANDs and ORs

You can nest AND and OR logic to whatever level of complexity you need. For example:

    // Build a query to find entities where:
    // (name begins with "Starbucks") OR (name begins with "Coffee")
    // OR
    // (name full text search matches on "tea" AND tel is not blank)
    $query = new FactualQuery;    
    $res = $factual->fetch("places", $query);

Geo Filters

Geo Filters provide the means to query Factual for entities located within a circle, rectangle, or near a point:

Circle (Point/Radius)

    // Find entities located within 5000 meters of a latitude, longitude
    $query->within(new FactualCircle(34.06018, -118.41835, 5000)); //lat, lon, radius
  • When using a point/radius geo filter, distance (in meters) from the point will be returned in the response packet under the $distance key. This distance is calculated as the crow flies.
  • Point/radius queries are implemented as a point at the center of a square with sides twice the radius.
  • The radius for point/radius queries is limited to 15 km.


    // Find entities located adjacent to a latitude, longitude
    $query->at(new FactualPoint(34.06018, -118.41835));
  • Point queries are just shortcuts for a circle query, with an implied radius of 500m.


    // Find entities located within a box over LA
    $query->within(new FactualRectangle(34.06110,-118.42283,34.05771,-118.41399)); 
  • Points order is [top,left],[bottom,right]
  • Points are always ordered as [latitude, longitude].

Notes on Geo Filters

  • Distance is always specified in meters
  • The maximum area that any geo query can encompass is 900 km2
  • Sorting by distance requires a special $distance operator. Be sure to escape the dollar sign:
    $query->sortAsc("\$distance"); //order results 

Search By Factual ID (FetchRow)

The fetchrow() method retrieves entities by Factual ID. Is a simple shortcut equivalent to a filter on Factual ID, and returns an array with one element like a regular read:

    //get started
    $factual = new Factual($key,$secret);
    //assign vars
    $factualID = "03c26917-5d66-4de9-96bc-b13066173c65";
    $tableName = "places";
    //fetch row
    $res = $factual->fetchRow($tableName, $factualID);

Paging Through Results: Limit and Offset

You can use limit and offset to support basic results paging. For example:

    // Build a Query with offset of 150, limiting the page size to 10:
    $query = new FactualQuery;
    $res = $factual->fetch("places", $query);

NOTE: the driver is designed to access Factual's API at runtime. We enforce a deep paging limit of 500 rows for any unique combination of filters:

This is the polite way of saying we'd rather you did not use our API to scrape Factual data for permanent retention. We do provide downloads of the entire dataset: contact

Total Row Count

Factual does not return the total number of records matching your filter by default -- there is a modest overhead in calculating this. We do however provide you the option of retrieving it explicitly.

To obtain the total number of all entities that meet your query criteria, set the parameter in the query object using FactualQuery::includeRowCount() method:

    $query = new FactualQuery;

After you've made the query using Factual::fetch(), the resultant number can be obtained with the ReadResponse::getTotalRowCount() call on the response object:

    $res = $factual->fetch("places", $query); 

API Documentation:

Sorting Results

Factual will sort your query results for you, on a field-by-field basis. Simple examples:

    // Build a Query to find 10 random entities and sort them by name, ascending:
    $query = new FactualQuery;
    $res = $factual->fetch("places", $query);

You can specify more than one sort, and the results will be sorted with the first sort as primary, the second sort or secondary, and so on:

    // Build a Query to find 20 random entities, sorted ascending primarily by region, then by locality, then by name:
    $query = new FactualQuery;
    $res = $factual->fetch("places", $query);

Sorting by distance requires a special $distance operator. Be sure to escape the dollar sign in PHP:

    $query->sortAsc("\$distance"); //order results 

Read API Documentation:


Facets is a special call that returns summary row counts grouped by values of a specific attribute -- think of this as a combined COUNT() and GROUP BY query in SQL.

Use Facets to analyze the results of your query by count: for example, you may wish to query all businesses within 500m of a location, group those businesses by category, and get a count of each.

Facets Example

//Finds the top twenty-five countries containing places with the string 'Starbucks'
$query = new FacetQuery("country"); //name the field to facet on in the constructor
$query->search("starbucks"); //search on 'Starbucks' using the usual paramateric filters
$query->limit(15); //show no more than 15 results
$query->minCountPerFacet(10); //only show countries with more than 10 results
$res = $factual->fetch("global", $query); //perform the query using Factual::fetch() as usual
print_r($res->getData()); //dump results out as an array

The response looks like:

    [country] => Array
        [us] => 11019
        [ca] => 902
        [gb] => 434
        [cn] => 194
        [de] => 174
        [tw] => 121
        [ph] => 78
        [au] => 69
        [tr] => 68
        [id] => 55
        [fr] => 47
        [sg] => 41
        [mx] => 33
        [ch] => 31
        [hk] => 27

You cannot facet on all fields, only those configured for faceting by Factual. Use the schema call to determine which fields can be faceted: if the faceted attribute of the schema is true, you can facet.

Facets Parameters

Parameter Description Example
select Array of comma-delimited string of field names on which facets should be generated, included as the constructor parameter to the FacetQuery. The response will not necessarily be ordered identically to this list, nor will it reflect any nested relationships between fields. $query = new FacetQuery("region,locality");
min_count Include only facets that have this minimum count. Must be zero or greater. The default is 1. $query->minCountPerFacet(2)
limit The maximum number of unique facet values that can be returned for a single field. Range is 1-250. The default is 20. $query->limit(10)

You can also employ the filters, include count, geo and search parameters with Facets, like any other Read query.

Facets API Documentation:


The Submit endpoint allows you to add a record to Factual, or to update an existing record. To delete a record, see the flag() method, below.

Unverified accounts are restricted from making submit API calls. Log in to Factual, and verify your account at


Strictly speaking, we do an 'UPSERT' when you contribute data: we determine if the entity already exists, and update it appropriately; if not we create a new entity. This avoids dupes and allows you to contribute data even if you do not know the Factual ID. However, if you do, please include it to remove any ambiguity using the FactualSubmittor::setFactualID() method. The only difference between updating an extant record and adding a new one is this inclusion of the Factual ID:


You can determine whether the entity you submitted is new:

    //is the submission a new entity?
    $isNew = $res->isNew();

However, It's always a good idea to obtain the Factual ID from a Submit Result, and store it against the submitted entity:

    //get Factual ID of submitted entity
    $factualID = $res->getFactualID();

We attempt to return a Factual ID with every Submit Result; it is good practice to make a note of this and store it, and verify it against the ID you submitted. In a few cases (such as if the entity you submitted has been deprecated), we may return a Factual ID different from the one you submitted. In very limited circumstances, submissions may not be matched to records in realtime, and thus no factual_id will be provided.

Submit Parameters

Parameter Description Required? Example
user An arbitrary token representing the end user who is submitting the data. best to keep this annymous; we don't want to know who your users are, but do use this token to model and weigh the quality of their individual contributions Yes setUserToken("387523")
values The data to submit; field names from the table schema, mapped to values Yes setValue("locality","Palo Alto")
setValue("address","425 Sherman Ave.")
comment Any english text comment that may help explain the submit No setComment("New Office")
reference A reference to a URL, title, person, or other source of the submitted data No setReference("")
strict If set to true, Factual will reject submissions that contain invalid fields. (default is false.) See below for more. No strict=true
clear_blanks If set to true, any field hashed to "" will be cleared. Use with care. (default is false.) See below for more. No strict=true

Strict Mode

By default, Factual's API will optimistically accept all field provided in the values parameter (with the exception of fields that have explicitly been marked as "unwriteable"). This makes it easier to pass what data you have to Factual, regardless of how precisely well it fits the schema of the table you are correcting.

But by being cool and froody, we introduce a downside: you will not be warned if data you are providing may be discarded due to simple mismatches between field names and what you've put in your values parameter. For example, if you misspell "category" as "catogory".

Setting the strict parameter to true will cause the system to automatically verify the names of the values you provided against the Factual schema. Any fields that do not match will cause your entire submission to automatically be rejected with a 400 error.

    //create submittor object and set strictMode to true
    $submitterator = new FactualSubmittor;
    $submitterator->strictMode(); //sets to true

    //set Factual ID and set new values
    $submitterator->setValue("addresss","80 Rue Masson"); //this will bork the query b/c the field name is invalid

    //make request
    $res = $factual->submit($submitterator);

Clear Blanks

By default Factual employs a separate call to clear a field of information. This is because it is so easy to send empty values with out really intending to clear the corresponding attribute. Using clearBlanks() circumvents this safeguard so that the clearing of fields can be accomplished in a single API call. For example:

    //create submittor object and set clearBlanks to true
    $submitterator = new FactualSubmittor;
    $submitterator->clearBlanks(); //sets to true

    //set Factual ID and set new values
    $submitterator->setFactualID("03c26917-5d66-4de9-96bc-b12066172c65"); //the Factual ID for our LA office
    $submitterator->setValue("address","80 Rue Masson");

    //make request
    $res = $factual->submit($submitterator);

will behave identically to sending a submit request with the name, address and a clear request for the address_extended, to update the address and remove the existing extended address.

Delayed Writes

Sometimes, if rarely, the write is cached and not written directly. In these instances, neither a commitID nor a Factual ID will be returned.

As this is an expected, if unusual program flow, it is best to check submissions with the isDelayed() method:

//make request
$res = $factual->submit($submitterator);
if (!$res->isDelayed()){
    //store Factual ID and Commit ID
} else {
    //do omething else

Submit Examples

Add data to Factual's Places table:

    //Create new submittor object and assign table to write to
    $submitterator = new FactualSubmittor;
    $tableName = "us-sandbox"; //the table we are writing to

    //add individual user token & table name (required)
    $submitterator->setUserToken("1235");   //this your assigned the token of the individual user
    $submitterator->setTableName($tableName); //the table name containing the record

    //add the values to update
    $submitterator->setValue("address","80 Rue Masson");

    //this other metadata is optional, but welcome
    $submitterator->setComment("This is a test update");

    //make request
    $res = $factual->submit($submitterator);

    //confirm status of submission
    if ($res->success()){
        if ($res->isDelayed){
            echo "OK, but delayed write\n";
        } else {
            echo "OK\n";
    } else {
        echo "Borked\n";

Add data as array:
This does the same as the previous example, but takes an associative array as parameter:

    //Create new submittor object and assign table to write to
    $submitterator = new FactualSubmittor;
    $tableName = "us-sandbox"; //the table we are writing to

    //add individual user token & table name (required)
    $submitterator->setUserToken("1235");   //this your assigned the token of the individual user
    $submitterator->setTableName($tableName); //the table name containing the record

    //add the values to update
    $data = array(
        'locality' => "Salaberry-de-Valleyfield",
        'address' => "80 Rue Masson"

    //this other metadata is optional, but welcome
    $submitterator->setComment("This is a test update");

    //make request
    $res = $factual->submit($submitterator);

    //confirm status of submission
    if ($res->success()){
        echo "OK\n";
    } else {
        echo "Borked\n";

Determine whether Factual considered your Submit to be a new entity:
Use SubmitResponse::isNew():

    echo "New Response?:". (bool)$res->isNew();

Correct the latitude and longitude of a specific entity in Factual's Places table:

    //Create new submittor object and assign table to write to
    $submitterator = new FactualSubmittor;
    $tableName = "us-sandbox"; 

    //add individual user token & table name (required)
    $submitterator->setUserToken("1235");   //this your assigned the token of the individual user
    $submitterator->setTableName($tableName); //the table name containing the record

    $submitterator->setFactualID("03c26917-5d66-4de9-96bc-b13066173c65"); //the Factual ID for our LA office

    //make request
    $res = $factual->submit($submitterator);

Correct the business name of a specific entity in Factual's Places table:

    //Create new submittor object and assign table to write to
    $submitterator = new FactualSubmittor;
    $tableName = "us-sandbox"; 

    //add individual user token & table name (required)
    $submitterator->setUserToken("1235");   //this your assigned the token of the individual user
    $submitterator->setTableName($tableName); //the table name containing the record

    //set values against specific Factual ID
    $submitterator->setFactualID("0cb6c5b0-cd40-012e-5616-003048cad9da"); //the Factual ID of the entity to change
    $submitterator->setValue("name", "W Austin");

    //make request
    $res = $factual->submit($submitterator);

Add a neighborhood to a specific entity in Factual's Places table:

    //Create new submittor object and assign table to write to
    $submitterator = new FactualSubmittor;
    $tableName = "us-sandbox"; 

    //add individual user token & table name (required)
    $submitterator->setUserToken("1235");   //this your assigned the token of the individual user
    $submitterator->setTableName($tableName); //the table name containing the record

    //set values against specific Factual ID
    $submitterator->setFactualID("0cb6c5b0-cd40-012e-5616-003048cad9da"); //the Factual ID of the entity to change
    $submitterator->setValue("neighborhood", "Downtown Austin");

    //make request
    $res = $factual->submit($submitterator);

Delete the neighborhood of a specific entity in Factual's Places table:

    //Create new submittor object and assign table to write to
    $submitterator = new FactualSubmittor;
    $tableName = "us-sandbox"; 

    //add individual user token & table name (required)
    $submitterator->setUserToken("1235");   //this your assigned the token of the individual user
    $submitterator->setTableName($tableName); //the table name containing the record

    //set values against specific Factual ID
    $submitterator->setFactualID("0cb6c5b0-cd40-012e-5616-003048cad9da"); //the Factual ID of the entity to change
    $submitterator->removeValue("neighborhood"); //yoink

    //make request
    $res = $factual->submit($submitterator);    

Submit API Documentation: Submit API documentation for Places:

Flagging Data for Editorial Attention

The Flag feature provides developers and editorial teams the ability to 'flag' problematic entities in tables for Factual editorial review. Use this feature to request an entity be deleted, flag an entity as a dupe or spam, note it does not exist, or just ask the Factual editors to check it out.

Flag Example

    //get started
    $factual = new Factual($key,$secret);
    //create a new flagger object to hold our parameters
    $flagger = new FactualFlagger;
    //add required parameters
    $flagger->setFactualID("f33527e0-a8b4-4808-a820-2686f18cb00c"); //ID to check
    $flagger->setTableName("2EH4Pz"); //name of table
    $flagger->setUserToken("testUser"); //arbitrary token of individual user
    //add optional parameters
    $flagger->setComment("Found by user");
    $flagger->setReference("Original entity on");
    //make request
    $res = $factual->flag($flagger);
    //check for success
    if ($res->success()){
        echo "OK\n";
    } else {
        echo "Borked\n";

Flag API Documentation:

Clearing Attribute Values

The Clear() method allows you to clear or remove attribute values from a Factual record.

Clear Parameters

Parameter Description Required? Example
user An arbitrary token representing the end user who is submitting the data. Yes $clear->setUserToken("twb")
fields The attribute fields to be cleared. Yes $clear->clearValues(array("longitude","latitude")) or $clear->clearValue("name")
comment Any text that may help explain the clear/submission. English only please. No $clear->setComment("submitted via email by owner");
reference A reference to a URL, title, person, etc. that is the source of the submitted data. No setReference("")

Clear Examples

Clear the value of the longitude and latitude in an existing entity:

    //create clearor object
    $clearor = new FactualClearor;

    //assign the ID and se the values to clear/wipe
    $clearor->setFactualID("1d93c1ed-8cf3-4d58-94e0-05bbcd827cba");//this is required

    //set tablename and other metadata about this submission
    $clearor->setTableName("us-sandbox"); //where can we find this entity
    $clearor->setUserToken("8363b7"); //the user/editor who is writing to us through you

    //make request
    $res = $factual->clear($clearor);

As an alternative you can use clearValues() to clear multiple attributes:

    $values = array("longitude","latitude");

Clear API Documentation:


Factual's Crosswalk feature lets you "crosswalk" the web by looking up Factual entities by the URL of other web authorities.

Note that as of v1.4.3, crosswalk requests are treated as any other table read -- this means that you can access the Crosswalk table using normal search and filters, as in the example below. As of v.1.5.0 all deprecated Crosswalk functions have been removed.

Places Crosswalk Example

    // Get all Crosswalk data for a specific Place, using its Factual ID:
    $query = new FactualQuery;    
    $res = $factual->fetch("crosswalk", $query);

Crosswalk API Documentation:


Factual Match allows you to match your own data against Factual's. We return the ID of the matching entity (only the ID) if we are certain of a match.

If you are looking to enrich your entities with additional data on matching, see the Factual Resolve. This service is designed for high-volume entity matching only. Match is almost identical to Resolve, but we only return Factual IDs, and queries do not count against users' quotas.

Generally the more information you provide the service, the better we are able to make a match.

Finding a Match

Use the common query structure to add known attributes to the query:

    //Build the query
    $query = new MatchQuery();
    $query->add("name", "Buena Vista Cigar Club");
    $query->add("latitude", 34.06);
    $query->add("longitude", -118.40);
    //perform the query
    $res = $factual->fetch("places", $query);

And then see if we found a match:

    $match = $res->getMatched()); //FALSE == no match, Factual ID == match  

Shortcut Method

Alternatively use the shortcut method in the Factual object:

    //assing tablename
    $tableName = "places";
    //create values array
    $vars = array(
        "name"=>"Buena Vista Cigar Club",
    $res = $factual->match($tableName,$vars);
    $match = $res->getMatched()); //FALSE == no match, Factual ID == match  

Match API Documentation:


Use Resolve to match your data against Factual's: we return everything we know about the entity allowing you to enrich or dedupe your own content.

Note that we provide a separate endpoint that returns Factual IDs only: Factual Match, above. Use Match when you want to attach Factual IDs to your entities at high volume and call quotas; use Resolve when you want to enrich your entities with our data.

Resolve Example

Use the common query structure to add known attributes to the query:

    // Get all entities that are possibly a match
    $query = new ResolveQuery();
    $query->add("name", "Buena Vista Cigar Club");
    $query->add("latitude", 34.06);
    $query->add("longitude", -118.40);
    $res = $factual->fetch("places", $query);   

And then use methods on the result object to determine resolution:

    //Did the entity resolve? (returns bool)
    $isResolved = $res->isResolved();

    //If so, get it:
    $resolvedEntity = $res->getResolved();

Shortcut Method

Alternatively use the shortcut to return the resolved entity OR null if no resolution:

    //Resolve and return
    $tableName = "places";
    $vars = array(
        "name"=>"Buena Vista Cigar Club",
    $res = $factual->resolve($tableName,$vars);
    print_r($res->getResolved()); //FALSE == no match, Array of entity data == resolved

Resolve API Documentation:

World Geographies

While Factual's places table provides access to the world's business and landmarks, our world-geographies table provides structured access to over 5.2 million geographies with 8.3 million name variants in 250 countries. Use our World Geographies table to lookup placenames, see how one place relates to another, and translate placenames between multiple languages.

World Geographies Example

    //find all localities (towns and cities) called "Wayne" in the US 
    $query = new FactualQuery;
    $query->field("placetype")->equal("locality");  //we don't want counties, etc.
    $query->only("name,placetype,longitude,latitude"); //"take only what you need from me.."(singing)
    $res = $factual->fetch("world-geographies", $query);

World Geographies Data Documentation:

Place Categorization

All Factual Places are classified into one of over 400 categories.

Searching by Categories

Search the category_id field:

    $category = "107"; //landmarks
    $query = new FactualQuery;  
    $query->field("category_ids")->in($category); //retrieves this category and all its descendants
    $res = $factual->fetch("places-us", $query);    

Places Category Documentation:

Global Products

Factual Global Products provides detailed product data for over 500,000 of the most popular consumer packaged goods in the US, including your favorite health, beauty, food, beverage, and household products. With Global Products, you can access key product attributes, find products using powerful search tools or UPC lookup, and connect to product pages across the web. See the Global Products API documentation for details on this fully operational battlestation.

Global Products Examples

    $tableName = "products-cpg";

    //Search for products containing the word "shampoo"
    $query = new FactualQuery;
    $res = $factual->fetch($tableName, $query); 

    //Same search as above, but filter the search results to include only the brand "pantene"
    $query = new FactualQuery;
    $query->field("brand")->equal("pantene"); //don't hate me because I'm beautiful
    $res = $factual->fetch($tableName, $query); 

    //Same search as above, with added filter for products that are 12.6 oz.
    $query = new FactualQuery;
    $query->field("size")->search("12.6 oz"); //we use 'search' b/c sometime it is 'fl oz' or just 'oz'
    $res = $factual->fetch($tableName, $query); 

    //Search on UPC
    $query = new FactualQuery;
    $res = $factual->fetch($tableName, $query); 

    //Find all beverages (filter by category)
    $query = new FactualQuery;
    $res = $factual->fetch($tableName, $query); 

    //Count all beverage products
    $query = new FactualQuery;
    $query->field("category")->equal("lip makeup");     
    $query->includeRowCount(); //this tells the API to calculate this value (modest overhead)
    $res = $factual->fetch($tableName, $query); 

Raw Requests

This driver primarily offers convenience: it signs requests, builds conformant queries, and structures responses.

However we do provide an option where the PHP Driver will perform OAuth authentication and parameter encoding only, allowing you to pass 'raw' key/value parameters to our API for debugging, implementing API features not yet codified in the driver, or simply satisfying masochistic tendencies.

These methods sign, encode, and submit the request. Responses are raw JSON, not a Factual response object.


Use for all GET operations, basically all queries and other read-only calls to the Factual service.

    //Raw Get Test
    $path = "t/global";
    $params['filters'] = "{\"\$and\":[{\"country\":{\"\$eq\":\"CA\"}},{\"locality\":{\"\$eq\":\"Toronto\"}}]}";
    $params['limit'] = 50;
    $params['include_count'] = true;
    $res = $factual->rawGet($path,$params);

In the above example we've escaped the JSON so it parses. As a cheeky but more lengthly alternative, you can create the filter as a nested array and json encode it, obviating the need to escape the JSON string:

    //create each filter as an array
    $countryFilter = array (
            'country'=> array (
    $localityFilter = array (
            'locality'=> array (
    //combine filters
    $filter = array(
        '$and' => array($countryFilter,$localityFilter) 
    $params['filters'] = json_encode($filter); //json encode

Generally tho, stick with the parametric filter methods; the raw reqest mode is really for debugging.


Use for all POST operations: primarily the Submit and Flag APIs.

    //Raw POST Test
    $path = "t/2EH4Pz/f33527e0-a8b4-4808-a820-2686f18cb00c/flag";
    $post['user'] = "testUser";
    $post['problem'] = "spam";
    $post['comment'] = "What do you mean 'Urgghh'? I don't like spam!";
    $res = $factual->rawPost($path,$post);


The Boost API helps Factual address the stateless aspect of HTTP to improve search results. It enables you to signal to Factual that a specific row returned by full-text search in a read API call should be a prominent result for that search. The Factual ID of the specified row does not need to be in the response to a read request. E.g., you may use boost to signal that a desired search result (identified by its Factual ID) is preferred.

This code picks up after the search. You'll need to squirrel away the query in the user session or similar:

  $factual = new Factual($key,$secret); 
    $boost = new FactualBoost; //create the boost object for populating
    $boost->setQueryString("chipotle palo alto"); //use for free text queries only
    $boost->setUserToken("user1234"); //anon. user token for this session. Optional, but helps us if poss.
    $boost->setFactualID("71df2b80-bef8-012e-5614-003048cad9da"); //the ID of the entity selected by the user
    $boost->setTableName("places-us"); //table the query was made against
    $res = $factual->boost($boost);
    //test for success. Just for debugging, of course.
    if ($res->success()){
        echo "Boosted";
    } else {
        echo "Borked";

Note that the boost API will not result in a real-time refinement of search results or a user-customized search experience. Boost simple enables longer term enhancement of overall search result quality through the Factual API.

Boost API Documentation:


The Diffs API is provided by Factual for use by our Download Data Partners -- those who are using a complete copy of Factual data remotely. The Diffs API produces atomic JSON updates to the remote dataset. See the Factual Diffs API documentation for a complete overview.

A Single Diff

A single diff consists of a complete JSON array. The diffs feed is a stream of these encapusltated diffs. A single diff:

    [factual_id] => 0249bc16-8912-4653-81a5-e07e0964c343
    [type] => update
    [payload] => Array
            [region] => ON
            [geocode_accuracy] => 7.5
            [fax] => (416) 781-2744
            [website] =>
            [tel] => (416) 781-9145
            [postcode] => M6B 3T8
            [country] => ca
            [category] => Services and Supplies > Home Improvement > Home Appliances
            [address] => 11160 Yonge St
            [name] => Tasco Distributors
            [locality] => Richmond Hill
            [longitude] => -79.4543685913
            [latitude] => 43.7132034302
    [changed] => Array
            [0] => tel
    [timestamp] => 1339136961334

The payload contains the entire record, while the changedelement highlights only those items that have been modified.

Getting Diffs

    //Getting started
    $factual = new Factual($key,$secret);

    //Getting Diffs
    $query = new DiffsQuery;
    $query->setStart(1339123455775); //starting time for updates window. Milliseconds timestamp.
    //$query->setEnd(1339136968687); //this is optional. Otherwise defaults to current time
    $res = $factual->fetch("2EH4Pz", $query);   //run query

Having run the Diffs query, there are a few things you can do with the resultant object:

Working with the Diffs Response Object

Getting Summary Statistics

    //show summary stats

returns an overview of the update counts and window duration:

    [insert] => 78
    [update] => 705
    [delete] => 56
    [deprecate] => 0
    [total] => 839
    [duration] => 09:35:19
    [start] => Thu, 07 Jun 2012 19:44:15 -0700
    [end] => Sun, 29 Jul 2012 01:20:13 -0700

Iterate Through the Diffs

The result object is an ArrayIterator, so you can walk through the results:

    foreach ($res as $diff){

outputs the diffs as an array, one at a time. You can grab all diffs as an array of diff arrays:

    $allDifs = $res->getDiffs();

Get Start and End Window Times

The result object provides additional methods to determine the specific start, end, and duration timestamps for the diffs window:

    $end = $res->getEnd() //get close of window as timestamp
    $duration = $res->getDuration() //get duration of window as timestamp
    $start = $res->getStart() //get start of window as timestamp

Include true as the parameter to get a human readable version:

    $end = $res->getEnd(true) //get close of window as human-readable RFC 2822
    $duration = $res->getDuration() //get duration of window as H:m:s
    $start = $res->getStart() //get start of window as tRFC 2822

Diffs API Documentation:

Multi Queries

Our 'Multi' feature allows you to make up to three queries via a single http request. Multi can combine queries of different types, but they all must be GET requests. See the Multi API documentation for details.

A Multi Example

Create your query objects as usual, and add them to the query queue using multiQueue():

    //create first query and add to queue
    $query1 = new FactualQuery;
    $factual->multiQueue("global", $query1, "globalQ"); //'globalQ' is the arbitrary handle for this query

    //create second query and add to queue
    $query2 = new FactualQuery;
    $factual->multiQueue("world-geographies", $query2, "worldGeo"); //'worldGeo' is the arbitrary handle for this query

Note that multiQueue() parameters are just like those of the fetch() method but include a required third parameter: an arbitrary string that you use to identify the results from each query. These must be unique to each query.

Use multiFetch() to send your request:

    //make multi request
    $res = $factual->multiFetch();

and iterate through the response to obtain each response object:

    //iterate through response objects
    foreach ($res as $queryResponse){

Multi API Documentation:

Working with Factual Data Files

Some Factual data partners work with our data drops rather than the API. Factual data files are provided in compressed, tab-delimited format and the filename includes a UNIX timestamp in milliseconds, ex:

Compression is gzip format -- it can be opened with most Windows, Linux, and Mac compression utilities.

This timestamp should be used for your first Diffs API call.

File Size

Factual files contain millions of records. If you're trying to use Excel, don't. Do not load the entire file into memory, and don't try to view with an editor - use head, tail, sed, awk, grep, and other utilities. These are all available for Windows at

Loading to SQL or Other System

Loading data into a database is pretty straightforward using the fgetcsv command:

    //get file pointer
    filename = "path/to/data/file";
    $fp = fopen($filename, "r");

    //iterate row-by-row
    while ($row = fgetcsv($fp,0, $delimiter)){
        //$row is now your first row of data
        // insert ROW into your db w/ SQL

Determining Field Width

Factual does not have formal specifications around field lengths -- there is always the possibility that field lengths will change between versions (but rarely by much). However, we do provide a CSV analysis script that allows you to calculate value lengths for any CSV.

If you are on Windows and don't want to run a PHP script, you can use a third-party Windows utility that does the same thing.


Brief instructions on how to install the Factual PHP driver as Wordpress plugin:

  1. Download the zip archive of the driver from Factual or git clone from and then zip archive it yourself.
  2. Go to the plugins page of the Wordpress Manager (/wordpress/wp-admin/plugins.php)
  3. Click Add New
  4. Click Upload
  5. Select the .zip archive of the driver (note: the plug-in manager will ONLY accept .zip files)
  6. Click Activate Plugin
  7. Create a test page, something like this:

    /** load factual driver. Make sure the path below matches what you have! */
    $factual = new Factual("YOUR_API_KEY","YOUR_API_SECRET");
    $query = new FactualQuery;
    $res = $factual->fetch("places-us", $query);
    $data = $res->getData();
    <h1>Hello. I found a place at:
    /** print the address of the first place */
  • Make sure you've put your own key and secret when you create the $factual object.
  • Make sure the import path matches the path to your plugin.
  • You'll need to pass a zip parameter to the URL with a US postcode.

If all goes well, you should get something like this:

Hello World Screenshot!