Google Click Fraud


All Goodtill stores have access to our API which can be used to:

Documentation for the API endpoints is available at


Authentication is handled by making a login call with credentials (subdomain, username and password) for an administrator or store owner user within the Goodtill store. This response will contain a JSON Web Token (JWT) which can be used to authorize future requests by passing it in the Authorization header (eg Authorization: Bearer token_here).

Tokens are short lived and must be refreshed to keep them active.

Certain endpoints require an extra Vendor-Id token – please request this token from if you require access to these endpoints.

Data format

Requests bodies may be encoded as JSON or x-www-form-encoded, however JSON should be used if possible.

Responses bodies are encoded as JSON.

Dates and times included in responses will be returned in the timezone configured in the Goodtill Store.


Stores can be configured with multiple outlets, which allows segregating data such as sales, products and stock quantities in different locations. Data entities can be store-wide (such a customers), outlet specific (such as sales) or optionally store-wide (such as products, via the Shareable toggle). The endpoint documentation states whether the entity available via the endpoint is outlet specific or store wide.

By default, all requests will fetch data from the outlet where the user account was created. It is possible to access data across multiple outlets using a single user account by specifying an outlet ID in the request, eg Outlet-Id: outlet_id_here. This will change the outlet where data is accessed from if the user has been granted access to the selected outlet (eg via outlet tagging or when using the store owner account which has access to all outlets). A list of outlets (including IDs) that the user has access to can be obtained via the outlets endpoint.

Rate Limiting

The API endpoints are not currently rate limited, however this is subject to change.


Your integration can get near real-time notifications of certain events in your Goodtill store, such as new sales, using webhooks.

Test Accounts

If you are building an integration and you would like to test the API without affecting your live account, or you are building an integration on behalf of a Goodtill customer, you request a fully-featured test account from Just include some details about the integration and which customer the integration is for.


If you have any questions, please contact

Example Code

The following is a minimal example of how you can fetch data using the Goodtill API using PHP.

The code does the following:

  • Obtain a JWT for accessing the Goodtill API.
  • Fetches the sales for a date range using multiple requests (due to pagination).
  • Export the list of sales items in the sale as a CSV.

You will need to enter the credentials for an admin or store owner and account and your verification token in the define() calls.

In order to keep this example simple, we will call the POST api/login endpoint each time this script is called. Ideally you would save the $goodtill_jwt in your database allowing you to reuse it until it needs to be refreshed.

// Set Goodtill credentials
define('GOODTILL_USERNAME', '');
define('GOODTILL_PASSWORD', '');

// Store login JWT for future requests
$goodtill_jwt = null;

 * Make API request to the Goodtill API, authenticated with JWT and
 * return the response body.
 * @param string $url 
 * @param string $method
 * @param array $data
 * @param null|string $outlet_id
 * @return null|array
function makeRequest($url, $method='GET', $data=[], $outlet_id=null)
    global $goodtill_jwt;

    $url = ''.$url;

    $body = null;
    $headers = ['Content-type: application/json'];

    if ($goodtill_jwt != null)
        $headers[] = 'Authorization: Bearer '.$goodtill_jwt;

    // Pass an outlet ID if we want to get data for a specific outlet,
    // rather than the outlet assigned to the user.
    if ($outlet_id != null)
        $headers[] = 'Outlet-Id: '.$outlet_id;

    // Add parameters to URL
    if ($method == 'GET'){
        if (empty($data) == false)
            $url .= '?'.http_build_query($data);
    // Add data to request body
    else if ($method == 'POST'){
        $body = json_encode($data);

    $opts = ['http' =>
            'method'  => $method,
            'header'  => implode("\r\n", $headers),
            'content' => $body,

    $context = stream_context_create($opts);
    $response = file_get_contents($url, false, $context);
    return json_decode($response, true);

// Get the request content
$body_json = file_get_contents('php://input');
$data = json_decode($body_json, true);

// Get a JWT for authorising future requests
$response = makeRequest(
        'subdomain' => GOODTILL_SUBDOMAIN,
        'username' => GOODTILL_USERNAME,
        'password' => GOODTILL_PASSWORD,

// Check for response error (eg invalid credentials)
if ($response == null)
    die('Login error - please check the credentials.');

// Check that an admin or store own account has been used
if ($response['user_level'] == 'operator')
    die('Operator logins cannot be used to make API request - use an admin or store owner account instead.');

// Set JWT for future requests. We'll need this to fetch the data (eg sale or customer)
// that we're being notified about.
$goodtill_jwt = $response['token'];

// Fetch the sales each page at a time and stop when we get to the end (no more sales returned).
$sales = [];
$limit = 50;
$offset = 0;
while (true) {
    $response = makeRequest('external/get_sales_details', 'GET', [
        'timezone' => 'local',
        'from' => '2019-01-01',
        'to' => '2019-01-08',
        'limit' => $limit,
        'offset' => $offset,

    if (empty($response['data']))

    $sales = array_merge($sales, $response['data']);
    $offset += $limit;

// Format the retrieved data as a CSV
$handle = fopen('php://memory', 'rw+');
fputcsv($handle, ['product_id', 'product_name', 'quantity', 'line_total_after_discount']);

foreach ($sales as $sale){
    foreach ($sale['sales_details']['sales_items'] as $sale_item)
    fputcsv($handle, [
        'product_id' => $sale_item['product_id'],
        'product_name' => $sale_item['product_name'],
        'quantity' => $sale_item['quantity'],
        'line_total_after_discount' => $sale_item['line_total_after_line_discount'],

// Get CSV from stream
fseek($handle, 0);
$csv = stream_get_contents($handle);
echo $csv;

Example output:

Need Further Helps?

We are always happy to help with any issues you may be having. If you can't find what you're looking for within our support portal please send us a message by clicking the button below or call our support team on 0203 322 4095.