Skip to content

Customer handling

To create a customer or modify her/his data, you need the customer resource endpoint from the OPTIONS request. Depending on the used routes, it might be something like this:

curl -b cookies.txt -c cookies.txt \
-X OPTIONS 'http://localhost:8000/jsonapi'
{
    "meta": {
        "csrf": {
            "name": "_token",
            "value": "..."
        },
        "prefix": "ai",
        "resources": {
            "customer": "http://localhost:8000/jsonapi/customer",
            ...
        }
    }
}

The "csrf" section in "meta" is important to modify the customer data. Each response will contain such a "csrf" section if the host application supports tokens against cross-site request forgery attacks. If available, you have to send them with every DELETE, PATCH and POST request.

Warning

Don't take the resource URLs in the OPTIONS response as granted! They will change depending on the routes and the application. Thus, your client needs the OPTIONS URL of the JSON REST API as configuration parameter. Its response will define the next possibilities.

Fetch customer data#

To get the customer entry, you have to send a GET request to the customer endpoint returned by the OPTIONS request:

curl -b cookies.txt -c cookies.txt \
-X GET 'http://localhost:8000/jsonapi/customer'
// returned from OPTIONS request
fetch(response.meta.resources['customer']).then(result => {
    if(!result.ok) {
        throw new Error(`Response error: ${response.status}`)
    }
    return result.json()
}).then(result => {
    console.log(result.data)
})
$.ajax({
    method: "GET",
    dataType: "json",
    url: response.meta.resources['customer'] // returned from OPTIONS request
}).done( function( result ) {
    console.log( result.data );
});

If the customer didn't authenticate herself/himself yet, an empty customer item is returned:

{
    "meta": {
        "total": 1,
        "prefix": null,
        "content-baseurl": "http://localhost:8000/",
        "content-baseurls": {
            "fs-media": "http://localhost:8000/aimeos",
            "fs-mimeicon": "http://localhost:8000/vendor/shop/mimeicons",
            "fs-theme": "http://localhost:8000/vendor/shop/themes"
        },
        "csrf": {
            "name": "_token",
            "value": "..."
        }
    },
    "links": {
        "self": "http://localhost:8080/jsonapi/customer",
        "customer/address": {
            "href": "http://localhost:8080/jsonapi/customer?related=address",
            "allow": ["GET","POST"]
        },
        "customer/property": {
            "href": "http://localhost:8000/jsonapi/customer?id=2&related=property",
            "allow": ["GET","POST"]
        },
        "customer/relationships": {
            "href": "http://localhost:8000/jsonapi/customer?id=2&related=relationships",
            "allow": ["GET","POST"]
        }
    },
    "data": {
        "id": null,
        "type": "customer",
        "links": {
            "self": {
                "href": "http://localhost:8080/jsonapi/customer",
                "allow": ["DELETE","GET","PATCH"]
            }
        },
        "attributes": {
            "customer.id": null,
            "customer.salutation": "",
            "customer.company": "",
            "customer.vatid": "",
            "customer.title": "",
            "customer.firstname": "",
            "customer.lastname": "",
            "customer.address1": "",
            "customer.address2": "",
            "customer.address3": "",
            "customer.postal": "",
            "customer.city": "",
            "customer.state": "",
            "customer.languageid": "",
            "customer.countryid": "",
            "customer.telephone": "",
            "customer.email": "",
            "customer.telefax": "",
            "customer.website": "",
            "customer.longitude": null,
            "customer.latitude": null,
            "customer.label": "",
            "customer.code": "",
            "customer.birthday": null,
            "customer.status": 1,
            "groups": []
        }
    },
    "included": []
}

Tip

The way how a user is authenticated depends very much on the PHP framework you use. Please have a look at the documentation of the respective framework of your choice, e.g. at Laravel Passport/Sanctum or Symfony Guard.

The response of an authenticated user contains the account data and the groups assigned to the user:

{
    "meta": {
    "total": 1,
    "prefix": null,
        "content-baseurl": "http://localhost:8000/",
        "content-baseurls": {
            "fs-media": "http://localhost:8000/aimeos",
            "fs-mimeicon": "http://localhost:8000/vendor/shop/mimeicons",
            "fs-theme": "http://localhost:8000/vendor/shop/themes"
        },
    "csrf": {
        "name": "_token",
        "value": "..."
    }

    },
    "links": {
        "self": "http://localhost:8000/jsonapi/customer",
        "customer/address": {
            "href": "http://localhost:8000/jsonapi/customer?id=2&related=address",
            "allow": ["GET","POST"]
        },
        "customer/property": {
            "href": "http://localhost:8000/jsonapi/customer?id=2&related=property",
            "allow": ["GET","POST"]
        },
        "customer/relationships": {
            "href": "http://localhost:8000/jsonapi/customer?id=2&related=relationships",
            "allow": ["GET","POST"]
        }
    },
    "data": {
        "id": "2",
        "type": "customer",
        "links": {
            "self": {
                "href": "http://localhost:8000/jsonapi/customer?id=2",
                "allow": ["DELETE","GET","PATCH"]
            }
        },
        "attributes": {
            "customer.id": "2",
            "customer.salutation": "mr",
            "customer.company": "Test company",
            "customer.vatid": "DE12345678",
            "customer.title": "Dr.",
            "customer.firstname": "Test",
            "customer.lastname": "User",
            "customer.address1": "Test street",
            "customer.address2": "1",
            "customer.address3": "2. floor",
            "customer.postal": "12345",
            "customer.city": "Test city",
            "customer.state": "HH",
            "customer.languageid": "de",
            "customer.countryid": "DE",
            "customer.telephone": "+49012345678",
            "customer.email": "example@aimeos.org",
            "customer.telefax": "+490123456789",
            "customer.website": "https://aimeos.org",
            "customer.longitude": 10.0,
            "customer.latitude": 50.0,
            "customer.label": "Test User",
            "customer.code": "example@aimeos.org",
            "customer.birthday": "2000-01-01",
        },
        "relationships": {
            "group": {
                "data": [{
                    "id": "1",
                    "type": "group",
                    "attributes": {
                        "customer.lists.id": "1",
                        "customer.lists.domain": "customer/group",
                        "customer.lists.refid": "1",
                        "customer.lists.datestart": null,
                        "customer.lists.dateend": null,
                        "customer.lists.config": [],
                        "customer.lists.position": 0,
                        "customer.lists.status": 1,
                        "customer.lists.type": "default"
                    },
                    "links": {
                        "self": {
                            "href": "http://localhost:8000/jsonapi/customer/group?id=2&related=relationships&relatedid=1",
                            "allow": []
                        }
                    }
                }]
            }
        }
    },
    "included": [{
        "id": "1",
        "type": "group",
        "attributes": {
            "group.id": "1",
            "group.code": "admin",
            "group.label": "Administrator"
        }
    }]
}

If you don't need all data, you can limit the fields returned by adding &fields[customer]=... to the URL, e.g. "&fields[customer]=customer.code,customer.email".

To also retrieve addresses or other related data, you can add the URL &include=... to the URL so that the related data is also returned in the response. Available related resources are:

  • customer/address : List of delivery addresses
  • customer/property : List of properties stored for the user
  • product : List of products from the favorite or watch list

Create new customer#

If the user isn't logged in, it's possible to create a new customer by sending the user data within a POST request to the customer endpoint from the OPTIONS response. The e-mail address is the only absolutely required field for new entries but it's a good idea to send data for these fields as well:

  • customer.lastname (required in admin backend)
  • customer.address1 (required in admin backend)
  • customer.city (required in admin backend)
  • customer.languageid (required by many payment gateways)
curl -b cookies.txt -c cookies.txt \
-X POST 'http://localhost:8000/jsonapi/customer?_token=...' \
-H 'Content-Type: application/json' \
-d '{"data": {
    "attributes": {
        "customer.code": "testuser@example.com",
        "customer.label": "Test user",
        "customer.salutation": "mr",
        "customer.company": "Example company",
        "customer.vatid": "DE123456789",
        "customer.title": "Dr.",
        "customer.firstname": "Test",
        "customer.lastname": "User",
        "customer.address1": "Test street",
        "customer.address2": "1",
        "customer.address3": "",
        "customer.postal": "12345",
        "customer.city": "Test city",
        "customer.state": "HH",
        "customer.countryid": "DE",
        "customer.languageid": "de",
        "customer.telephone": "+4912345678",
        "customer.telefax": "+49123456789",
        "customer.email": "testuser@example.com",
        "customer.website": "https://example.com",
        "customer.longitude": 10.0,
        "customer.latitude": 50.0,
        "customer.birthday": "2000-01-01",
        "customer.password": "secret123"
    }
}}'
let params = {'data': [{
    attributes: {
        "customer.code": "testuser@example.com", // (optional, customer.email is used if empty)
        "customer.label": "Test user", // (optional, will be generated if empty)
        "customer.salutation": "mr", // "mr", "mrs", "miss", "company" or empty (optional)
        "customer.company": "Example company", // (optional)
        "customer.vatid": "DE123456789", // (optional)
        "customer.title": "Dr.", // (optional)
        "customer.firstname": "Test", // (optional)
        "customer.lastname": "User", // (required)
        "customer.address1": "Test street", // (required)
        "customer.address2": "1", // (optional)
        "customer.address3": "", // (optional)
        "customer.postal": "12345", // (optional)
        "customer.city": "Test city", // (required)
        "customer.state": "HH", // (optional)
        "customer.countryid": "DE", // (optional)
        "customer.languageid": "de", // (required by many payment gateways)
        "customer.telephone": "+4912345678", // (optional)
        "customer.telefax": "+49123456789", // (optional)
        "customer.email": "testuser@example.com", // (required)
        "customer.website": "https://example.com", // (optional)
        "customer.longitude": 10.0, // (optional, float value)
        "customer.latitude": 50.0, // (optional, float value)
        "customer.birthday": "2000-01-01", // (optional)
        "customer.password": "secret123" // (optional, generated if empty)
    }
}]}

let url = response['links']['customer']['href'] // from OPTIONS response

if(response['meta']['csrf']) { // add CSRF token if available and therefore required
    const csrf = {}
    csrf[response['meta']['csrf']['name']] = response['meta']['csrf']['value']
    url += (url.indexOf('?') === -1 ? '?' : '&') + window.param(csrf) // from https://github.com/knowledgecode/jquery-param
}

fetch(url, {
    method: "POST",
    body: JSON.stringify(params)
}).then(result => {
    if(!result.ok) {
        throw new Error(`Response error: ${response.status}`)
    }
    return result.json()
}).then(result => {
    console.log(result.data)
})
var params = {data: {
    attributes: {
        "customer.code": "testuser@example.com", // (optional, customer.email is used if empty)
        "customer.label": "Test user", // (optional, will be generated if empty)
        "customer.salutation": "mr", // "mr", "mrs", "miss", "company" or empty (optional)
        "customer.company": "Example company", // (optional)
        "customer.vatid": "DE123456789", // (optional)
        "customer.title": "Dr.", // (optional)
        "customer.firstname": "Test", // (optional)
        "customer.lastname": "User", // (required)
        "customer.address1": "Test street", // (required)
        "customer.address2": "1", // (optional)
        "customer.address3": "", // (optional)
        "customer.postal": "12345", // (optional)
        "customer.city": "Test city", // (required)
        "customer.state": "HH", // (optional)
        "customer.countryid": "DE", // (optional)
        "customer.languageid": "de", // (required by many payment gateways)
        "customer.telephone": "+4912345678", // (optional)
        "customer.telefax": "+49123456789", // (optional)
        "customer.email": "testuser@example.com", // (required)
        "customer.website": "https://example.com", // (optional)
        "customer.longitude": 10.0, // (optional, float value)
        "customer.latitude": 50.0, // (optional, float value)
        "customer.birthday": "2000-01-01", // (optional)
        "customer.password": "secret123" // (optional, generated if empty)
    }
}};

var url = response['links']['customer']['href']; // from OPTIONS response

if(response['meta']['csrf']) { // add CSRF token if available and therefore required
    var csrf = {};
    csrf[response['meta']['csrf']['name']] = response['meta']['csrf']['value'];
    url += (url.indexOf('?') === -1 ? '?' : '&') + $.param(csrf);
}

$.ajax({
    url: url,
    method: "POST",
    dataType: "json",
    data: JSON.stringify(params)
}).done( function( result ) {
    console.log( result );
});

You can't set the "customer.status" and "groups" properties for an account using the JSON API. If you do so, they will be ignored because, obviously, this would enable attackers to re-enable their disabled account or gain additional privileges.

In case the new account has been successfully created, the response will be similar to this one:

{
"meta": {
    "total": 1,
    "prefix": null,
        "content-baseurl": "http://localhost:8000/",
        "content-baseurls": {
            "fs-media": "http://localhost:8000/aimeos",
            "fs-mimeicon": "http://localhost:8000/vendor/shop/mimeicons",
            "fs-theme": "http://localhost:8000/vendor/shop/themes"
        },
    "csrf": {
        "name": "_token",
        "value": "..."
        }
    },
    "links": {
        "self": "http://localhost:8000/jsonapi/customer"
    },
    "data": {
        "id":"6",
        "type":"customer",
        "links":{
            "self":{
                "href":"http://localhost:8000/jsonapi/customer?id=6",
                "allow":["DELETE","GET","PATCH"]
            }
        },
        "attributes":{
            "customer.id":"6"
        }
    },
    "included": []
}

Only the ID of the newly created user account is returned for security reasons. Also, the number of user accounts that can be created from one IP address is limited to three accounts within four hours. You can change the limits using these settings:

Change customer data#

Once an account has been created and the user is logged in, the JSON API allows you to update the data in the user account.

Tip

How to authenticate the user depends on the used PHP framework. Please have a look into the documentation of your used framework, e.g. at Laravel Passport/Sanctum or Symfony Guard.

The request for changing data is very similar to creating a new user but it requires a PATCH request:

curl -b cookies.txt -c cookies.txt \
-X PATCH 'http://localhost:8000/jsonapi/customer?_token=...' \
-H 'Content-Type: application/json' \
-d '{"data": {
    "attributes": {
        "customer.code": "testuser@example.com",
        "customer.label": "Test user",
        "customer.salutation": "mr",
        "customer.company": "Example company",
        "customer.vatid": "DE123456789",
        "customer.title": "Dr.",
        "customer.firstname": "Test",
        "customer.lastname": "User",
        "customer.address1": "Test street",
        "customer.address2": "2",
        "customer.address3": "",
        "customer.postal": "12345",
        "customer.city": "Test city",
        "customer.state": "HH",
        "customer.countryid": "DE",
        "customer.languageid": "de",
        "customer.telephone": "+4912345678",
        "customer.telefax": "+49123456789",
        "customer.email": "testuser@example.com",
        "customer.website": "https://example.com",
        "customer.longitude": 10.0,
        "customer.latitude": 50.0,
        "customer.birthday": "2000-01-01",
        "customer.password": "very+secret"
    }
}}'
let params = {'data': [{
    attributes: {
        "customer.code": "testuser@example.com", // (optional, customer.email is used if empty)
        "customer.label": "Test user", // (optional, will be generated if empty)
        "customer.salutation": "mr", // "mr", "mrs", "miss", "company" or empty (optional)
        "customer.company": "Example company", // (optional)
        "customer.vatid": "DE123456789", // (optional)
        "customer.title": "Dr.", // (optional)
        "customer.firstname": "Test", // (optional)
        "customer.lastname": "User", // (required)
        "customer.address1": "Test street", // (required)
        "customer.address2": "2", // (optional)
        "customer.address3": "", // (optional)
        "customer.postal": "12345", // (optional)
        "customer.city": "Test city", // (required)
        "customer.state": "HH", // (optional)
        "customer.countryid": "DE", // (optional)
        "customer.languageid": "de", // (required by many payment gateways)
        "customer.telephone": "+4912345678", // (optional)
        "customer.telefax": "+49123456789", // (optional)
        "customer.email": "testuser@example.com", // (required)
        "customer.website": "https://example.com", // (optional)
        "customer.longitude": 10.0, // (optional, float value)
        "customer.latitude": 50.0, // (optional, float value)
        "customer.birthday": "2000-01-01", // (optional)
        "customer.password": "very+secret" // (optional, generated if empty)
    }
}]}

let url = response['links']['customer']['href'] // from OPTIONS response

if(response['meta']['csrf']) { // add CSRF token if available and therefore required
    const csrf = {}
    csrf[response['meta']['csrf']['name']] = response['meta']['csrf']['value']
    url += (url.indexOf('?') === -1 ? '?' : '&') + window.param(csrf) // from https://github.com/knowledgecode/jquery-param
}

fetch(url, {
    method: "PATCH",
    body: JSON.stringify(params)
}).then(result => {
    if(!result.ok) {
        throw new Error(`Response error: ${response.status}`)
    }
    return result.json()
}).then(result => {
    console.log(result.data)
})
var params = {data: {
    attributes: {
        "customer.code": "testuser@example.com", // (optional, customer.email is used if empty)
        "customer.label": "Test user", // (optional, will be generated if empty)
        "customer.salutation": "mr", // "mr", "mrs", "miss", "company" or empty (optional)
        "customer.company": "Example company", // (optional)
        "customer.vatid": "DE123456789", // (optional)
        "customer.title": "Dr.", // (optional)
        "customer.firstname": "Test", // (optional)
        "customer.lastname": "User", // (required)
        "customer.address1": "Test street", // (required)
        "customer.address2": "2", // (optional)
        "customer.address3": "", // (optional)
        "customer.postal": "12345", // (optional)
        "customer.city": "Test city", // (required)
        "customer.state": "HH", // (optional)
        "customer.countryid": "DE", // (optional)
        "customer.languageid": "de", // (required by many payment gateways)
        "customer.telephone": "+4912345678", // (optional)
        "customer.telefax": "+49123456789", // (optional)
        "customer.email": "testuser@example.com", // (required)
        "customer.website": "https://example.com", // (optional)
        "customer.longitude": 10.0, // (optional, float value)
        "customer.latitude": 50.0, // (optional, float value)
        "customer.birthday": "2000-01-01", // (optional)
        "customer.password": "very+secret" // (optional, generated if empty)
    }
}};

var url = response['links']['customer']['href']; // from OPTIONS response

if(response['meta']['csrf']) { // add CSRF token if available and therefore required
    var csrf = {};
    csrf[response['meta']['csrf']['name']] = response['meta']['csrf']['value'];
    url += (url.indexOf('?') === -1 ? '?' : '&') + $.param(csrf);
}

$.ajax({
    url: url,
    method: "PATCH",
    dataType: "json",
    data: JSON.stringify(params)
}).done( function( result ) {
    console.log( result );
});

The response will include the basic customer data including groups like in this example:

{
    "meta": {
        "total": 1,
        "prefix": null,
        "content-baseurl": "http://localhost:8000/",
        "content-baseurls": {
            "fs-media": "http://localhost:8000/aimeos",
            "fs-mimeicon": "http://localhost:8000/vendor/shop/mimeicons",
            "fs-theme": "http://localhost:8000/vendor/shop/themes"
        },
        "csrf": {
            "name": "_token",
            "value": "..."
        }
    },
    "links": {
        "self": "http://localhost:8000/jsonapi/customer?id=1"
    },
    "data": {
        "id": "2",
        "type": "customer",
        "links": {
            "self": {
                "href": "http://localhost:8000/jsonapi/customer?id=2",
                "allow": ["DELETE","GET","PATCH"]
            }
        },
        "attributes": {
            "customer.id": "2",
            "customer.code": "testuser@example.com",
            "customer.label": "Test user",
            "customer.salutation": "mr",
            "customer.company": "Example company",
            "customer.vatid": "DE123456789",
            "customer.title": "Dr.",
            "customer.firstname": "Test",
            "customer.lastname": "User",
            "customer.address1": "Test street",
            "customer.address2": "2",
            "customer.address3": "",
            "customer.postal": "12345",
            "customer.city": "Test city",
            "customer.state": "HH",
            "customer.countryid": "DE",
            "customer.languageid": "de",
            "customer.telephone": "+4912345678",
            "customer.telefax": "+49123456789",
            "customer.email": "testuser@example.com",
            "customer.website": "https://example.com",
            "customer.longitude": 10.0,
            "customer.latitude": 50.0,
            "customer.birthday": "2000-01-01",
            "customer.status": 1,
            "groups": ["1"]
        },
        "relationships": {
            "group": {
                "data": [{
                    "id": "1",
                    "type": "group",
                    "attributes": {
                        "customer.lists.id": "1",
                        "customer.lists.domain": "customer/group",
                        "customer.lists.refid": "1",
                        "customer.lists.datestart": null,
                        "customer.lists.dateend": null,
                        "customer.lists.config": [],
                        "customer.lists.position": 0,
                        "customer.lists.status": 1,
                        "customer.lists.type": "default"
                    },
                    "links": {
                        "self": {
                            "href": "http://localhost:8000/jsonapi/customer/group?id=2&related=relationships&relatedid=1",
                            "allow": ["DELETE","PATCH"]
                        }
                    }
                }]
            }
        }
    },
    "included": [{
        "id": "1",
        "type": "group",
        "attributes": {
        "group.id": "1",
        "group.code": "customer",
        "group.label": "Customer"
        }
    }]
}

It's not necessary to pass all customer fields along with the PATCH request. Instead, you can only add those fields with new or changed data. All other customer fields will remain unchanged.

Warning

In most frameworks the e-mail address is used as unique identifier. Therefore, customer.code and customer.email should contain the same value if both are sent within the request. Otherwise, this may lead to inconsistencies when using different frameworks as host application.

Delete customer#

It's also possible to delete a user account and remove all associated data (like addresses), except orders. All order related data will stay untouched because it's only loosely related to customer accounts and must be available for accounting reasons.

Every user can only delete his own account, and she/he must be logged in to do so.

Tip

The way a user is authenticated depends very much on the PHP framework you use. Please have a look at the documentation of the respective framework of your choice, e.g. at Laravel Passport/Sanctum or Symfony Guard.

For deleting the account use e.g.:

curl -b cookies.txt -c cookies.txt \
-X DELETE 'http://localhost:8000/jsonapi/customer?id=...&_token=...'
let url = response.links.self.href // from customer response

if(response['meta']['csrf']) { // add CSRF token if available and therefore required
    const csrf = {}
    csrf[response['meta']['csrf']['name']] = response['meta']['csrf']['value']
    url += (url.indexOf('?') === -1 ? '?' : '&') + window.param(csrf) // from https://github.com/knowledgecode/jquery-param
}

fetch(url, {
    method: "DELETE"
}).then(result => {
    if(!result.ok) {
        throw new Error(`Response error: ${response.status}`)
    }
    return result.json()
}).then(result => {
    console.log(result.data)
})
var url = response.links.self.href; // from customer response

if(response['meta']['csrf']) { // add CSRF token if available and therefore required
    var csrf = {};
    csrf[response['meta']['csrf']['name']] = response['meta']['csrf']['value'];
    url += (url.indexOf('?')= -1 ? '?' : '&') + $.param(csrf);
}

$.ajax({
    url: url, // returned from OPTIONS request
    method: "DELETE",
    dataType: "json"
}).done( function( result ) {
    console.log( result.data );
});

Note

Deleting a user returns a successful response even if the user doesn't exist or you are not allowed to delete that user for security reasons. If you want to be sure the users' own account is deleted, perform a GET request to the customer endpoint. It will return an empty customer item with no customer.id value if the account doesn't exist any more.

Comments

Become an Aimeos Partner

Aimeos partners are first-class specialists in creating or hosting your Aimeos e-commerce project. They have proven their expertise by building top level e-commerce applications using Aimeos.