Introduction ============ Informations sur l'API ---------------------- Notre API est construite à partir du framework `django-rest-framework`_. Elle est actuellement disponible en ``http`` et ``https``. Les différents endpoints sont catégorisés dans des index de endpoints. Par exemple, en envoyant une requête ``GET`` sur l'index `/restapi/quotation/`, vous obtiendrez la liste de tous les endpoints qu'elle contient. .. code-block:: bash $ curl -XGET https://www.aladom.fr/restapi/offers/ .. code-block:: json {"job-offer/my":"http://www.aladom.fr/restapi/offers/job-offer/my/","subcategory":"http://www.aladom.fr/restapi/offers/subcategory/"} .. note:: Pour améliorer la lisibilité des exemples de cette documentation, la sortie des commandes curl sera filtrée par la commande ``python -m json.tool`` de manière à indenter proprement le JSON. .. code-block:: bash $ curl -s -XGET https://www.aladom.fr/restapi/offers/ | python -m json.tool .. code-block:: json { "job-offer/my": "http://www.aladom.fr/restapi/offers/job-offer/my/", "subcategory": "http://www.aladom.fr/restapi/offers/subcategory/" } Description des endpoints ------------------------- Il est possible d'obtenir une description formelle de chaque endpoint en utilisant la méthode ``OPTIONS``. .. code-block:: bash $ curl -XOPTIONS https://www.aladom.fr/restapi/offers/subcategory/ .. code-block:: json { "name": "Subcategory List", "description": "", "renders": [ "application/json", "text/html" ], "parses": [ "application/json", "application/x-www-form-urlencoded", "multipart/form-data" ] } Cette description contient le nom du endpoint et une description si nécessaire. La clé ``renders`` donne les types mime dans lesquelles la réponse peut être formattée (à renseigner dans le header ``Accept`` de votre requête si le type par défaut ne vous convient pas). La plupart du temps les types de réponse sont ``application/json`` pour les appels d'API "réels" et ``text/html`` pour les appels faits via les navigateur. Il est en effet possible de consulter l'API via votre navigateur : https://www.aladom.fr/restapi/offers/subcategory/ Authentification ---------------- Certains endpoints nécessitent cependant une authentification. Si vous êtes connecté sur votre compte aladom, vous pourrez accéder à l'interface web de l'API. Mais pour un appel classique d'API (via curl par exemple), vous devrez utiliser le token d'authentification qui vous a été fourni par Aladom. .. code-block:: bash $ curl -XOPTIONS https://www.aladom.fr/restapi/offers/job-offer/my/ HTTP/1.1 401 Unauthorized Content-Type: application/json Allow: GET, POST, HEAD, OPTIONS WWW-Authenticate: Token .. code-block:: json { "detail": "Informations d'authentification non fournies." } .. code-block:: bash $ token='fedcba9876543210deadbeef0123456789abcdef' $ curl -H"Authorization: Token $token" -XOPTIONS https://www.aladom.fr/restapi/offers/job-offer/my/ HTTP/1.1 200 OK Content-Type: application/json Allow: GET, POST, HEAD, OPTIONS .. code-block:: json { "name": "My Job Offer List", "description": "", "renders": [ "application/json", "text/html" ], "parses": [ "application/json", "application/x-www-form-urlencoded", "multipart/form-data" ], "actions": { "POST": { "id": { "type": "integer", "required": false, "read_only": true, "label": "Id" }, "city": { "type": "field", "required": true, "read_only": false, "label": "Ville", "help_text": "Vous pouvez entrer l'ID de la ville sous forme d'entier, le code INSEE sous forme textuelle, ou bien l'alias (slug) sous forme textuelle." }, "provider": { "type": "integer", "required": true, "read_only": false, "label": "Annonceur" }, "title": { "type": "string", "required": true, "read_only": false, "label": "Titre", "max_length": 255 }, "published_on": { "type": "datetime", "required": false, "read_only": true, "label": "Publi\u00e9e le" }, "state": { "type": "choice", "required": false, "read_only": true, "label": "\u00c9tat" }, "hourly_wage": { "type": "float", "required": false, "read_only": false, "label": "Salaire horaire (brut)" }, "wage": { "type": "json", "required": false, "read_only": false, "label": "info salaire", "wage_type": { "type": "multiple choice", "required": false, "read_only": false, "label": "Type de salaire", "choices": [ { "value": 1, "display_name": "Horaire" }, { "value": 2, "display_name": "Mensuel" }, { "value": 3, "display_name": "Annuel" }, ] }, "wage": { "type": "float", "required": false, "read_only": false, "label": "Salaire", }, }, "application_email": { "type": "email", "required": false, "read_only": false, "label": "E-mail pour candidater", "max_length": 255 }, "contract_types": { "type": "multiple choice", "required": false, "read_only": false, "label": "Types de contrat", "choices": [ { "value": 1, "display_name": "CDI" }, { "value": 2, "display_name": "CDD" }, { "value": 3, "display_name": "Int\u00e9rim" }, { "value": 4, "display_name": "Stage" }, { "value": 5, "display_name": "Apprentissage" }, { "value": 6, "display_name": "Autre" } ] }, "url": { "type": "field", "required": false, "read_only": true, "label": "Url" } } } } La réponse ci-dessus (qui a été tronquée pour ne pas prendre trop de place) présente la clé ``actions`` qui liste les champs qu'il est possible de récupérer ou envoyer. Chaque champ est décrit par : - ``type`` le type de donnée attendue - ``required`` le caractère obligatoire ou non - ``read_only`` la possibilité ou non de modifier ce champ - ``label`` le nom du champ en français - ``help_text`` une éventuelle précision concernant ce champ Certains types de champs précisent également d'autres informations comme les choix possibles par exemple. Récupérer des données --------------------- Il existe deux grands types de endpoints permettant de récupérer des données. Les endpoints de type "liste" et ceux de type "item". Récupérer une liste ################### Les endpoints proposant une liste contenant peu d'éléments renvoient simplement un tableau JSON. Par exemple : .. code-block:: bash $ curl -XGET https://www.aladom.fr/restapi/offers/subcategory/ .. code-block:: json [ { "id": 1, "name": "Aide aux personnes handicap\u00e9es", "slug": "aide-personnes-handicapees", "category": "Aide au handicap", "category_id": 1 }, { "id": 2, "name": "Langue des signes", "slug": "langue-des-signes", "category": "Aide au handicap", "category_id": 1 }, { "id": 3, "name": "Transport handicap\u00e9", "slug": "transport-handicape", "category": "Aide au handicap", "category_id": 1 } ] Dans certains cas cependant, le nombre de résultats est potentiellement trop grand pour être renvoyé en une seule réponse et doit être paginé. Dans ce cas, cet un dictionnaire qui est renvoyé avec les clés suivantes : - ``count`` le nombre total de résultats - ``next`` url vers la page suivante le cas échéant - ``previous`` url vers la page précédente le cas échéant - ``results`` la liste des résultats .. code-block:: bash $ curl -XGET https://www.aladom.fr/restapi/offers/job-offer/my/ .. code-block:: json { "count": 33233, "next": "http://www.aladom.fr/restapi/offers/job-offer/my/?limit=100&offset=100", "previous": null, "results": [ { "id": 123456, "subcategory": { "id": 26, "slug": "cours-economie", "name": "Cours d'\u00e9conomie" }, "city": { "id": 30640, "code": "74152", "slug": "lovagny", "name": "Lovagny" }, }, { "id": 654321, } ] } Récupérer un item ################# Pour récupérer un item en particulier en connaissant son ID, il suffit de rajouter l'ID à la fin de l'URL : .. code-block:: bash $ curl -XGET https://www.aladom.fr/restapi/offers/subcategory/26/ .. code-block:: json { "id": 26, "name": "Cours d'\u00e9conomie", "slug": "cours-economie", "category": "Soutien scolaire", "category_id": 5 } Envoyer de données ------------------ Il y a également deux grands types de endpoints permettant d'envoyer des données. Les endpoints de création et ceux de modification. Création d'un item ################## La création d'un item se fait en utilisant la méthode ``POST`` sur l'URL de listing. Les données doivent être envoyées au format JSON et respecter la spécification donnée par la méthode ``OPTIONS``. En cas de succès, vous obtiendrez une réponse ``201`` au format JSON, contenant les détails de l'objet créé. .. code-block:: bash $ curl -XPOST -H'Content-Type:application/json' -d '{"city": "rennes", "provider": 153514, "title": "test", "subcategory": 33, "content": "Test Test"}' https://www.aladom.fr/restapi/offers/job-offer/my/ .. code-block:: json { "adhoc_mission_hours": null, "adhoc_mission_price": null, "application_email": "", "application_phone": "", "city": { "code": "35238", "id": 13811, "name": "Rennes", "slug": "rennes" }, "content": "Test Test", "contract_duration": null, "contract_types": [], "cv_required": false, "hourly_wage": null, "id": 1947561, "is_adhoc_mission": false, "is_full_time": false, "is_part_time": false, "latitude": null, "longitude": null, "minimum_age": 18, "minimum_experience": null, "origin_reference": "", "origin_source": "", "pole_emploi_id": null, "pole_emploi_reject_reason": "", "pole_emploi_state": "", "provider": 153514, "published_on": "2018-02-15T11:14:58.525928", "start_date": null, "state": 1, "subcategory": { "id": 33, "name": "M\u00e9nage", "slug": "menage" }, "title": "test", "url": "http://www.aladom.fr/emploi/test-rennes-15qqx.html", "vehicle_required": false, "weekly_hours": null } En cas d'erreur vous obtiendrez une réponse ``400`` au format JSON. Elle contiendra un dictionnaire dont chaque clé sera un des champs erroné et la valeur sera un tableau contenant la description en français des erreurs rencontrées sur le champ en question. .. code-block:: bash $ curl -XPOST -H'Content-Type:application/json' -d '{"city": "rennes", "provider": 15354, "title": "test", "subcategory": 33}' https://www.aladom.fr/restapi/offers/job-offer/my/ HTTP/1.1 400 Bad Request Content-Type: application/json .. code-block:: json { "provider": [ "Vous ne gérez pas le compte du membre '15354'" ], "content": [ "Ce champ est obligatoire." ] } Modification d'un item ###################### La modification d'un item se fait par la méthode ``PATCH`` sur l'URL d'item. Elle fonctionne de la même manière que la création d'un item, à l'exception que les champs que vous ne souhaitez pas modifier ne sont pas requis. .. code-block:: bash $ curl -XPATCH -H'Content-Type: application/json' -d '{"city": "35278", "title": "Test"}' https://www.aladom.fr/restapi/offers/job-offer/my/1947561/ .. code-block:: json { "adhoc_mission_hours": null, "adhoc_mission_price": null, "application_email": "", "application_phone": "", "city": { "code": "35278", "id": 14146, "name": "Saint-Gr\u00e9goire", "slug": "saint-gregoire" }, "content": "Test Test", "contract_duration": null, "contract_types": [], "cv_required": false, "hourly_wage": null, "id": 1947561, "is_adhoc_mission": false, "is_full_time": false, "is_part_time": false, "latitude": null, "longitude": null, "minimum_age": 18, "minimum_experience": null, "origin_reference": "", "origin_source": "", "pole_emploi_id": null, "pole_emploi_reject_reason": "", "pole_emploi_state": "", "provider": 153514, "published_on": "2018-02-15T11:14:58.525928", "start_date": null, "state": 1, "subcategory": { "id": 33, "name": "M\u00e9nage", "slug": "menage" }, "title": "Test", "url": "http://www.aladom.fr/emploi/test-rennes-15qqx.html", "vehicle_required": false, "weekly_hours": null } Types de champs --------------- integer ####### Les champs de type ``integer`` représentent un entier, présenté en tant que tel au format JSON. Ces champs peuvent définir une valeur minimum et une valeur maximum, respectivement avec les clés ``max_value`` et ``min_value``. float ##### Les champs de type ``float`` représentent un nombre réel, présenté en tant que tel au format JSON. Ces champs peuvent définir une valeur minimum et une valeur maximum, respectivement avec les clés ``max_value`` et ``min_value``. boolean ####### Les champs de type ``boolean`` représentent une information binaire pouvant prendre la valeur ``true`` ou ``false``. string ###### Les champs de type ``string`` représentent une chaine de caractères, présentée en tant que tel au format JSON. Le nombre de caractères peut être limité, ce qui est spécifiée par la clé ``max_length``. email ##### Les champs de type ``email`` sont des champs de type ``string`` destinés à recevoir une adresse e-mail. datetime ######## Les champs de type ``datetime`` sont des champs de type ``string`` destinés à recevoir un horodatage au format ISO-8601. choice ###### Les champs de type ``choice`` sont des champs dont les valeurs possibles sont limitées. La liste des choix possibles est précisée par la clé ``choices``. La clé ``choices`` se présente sous la forme d'une liste dont chaque élément est un choix possible, décrit par la clé ``value`` (la valeur effective) et la clé ``display_name`` (la signification en français). multiple choice ############### Les champs de type ``multiple choice`` sont similaires aux champs de type ``choice`` mais se présentent sous la forme d'un tableau JSON, permettant de sélectionner plusieurs choix parmis les valeurs indiquées par la clé ``choices``. field ##### Les champs de type ``field`` sont des champs personnalisés. Si le nom du champ n'est pas suffisamment explicite, la valeur attendue est précisée dans la clé ``help_text``. nested object ############# Les champs de type ``nested object`` sont des champs qui contiennent eux-mêmes d'autres champs. La liste des champs imbriqués possibles est définie par la clé ``children``. .. _django-rest-framework: http://www.django-rest-framework.org/ .. _webhooks: Webhooks -------- Il vous est possible de mettre en place des webhooks de manière à être notifiés en direct lorsque certains événements se produisent de notre côté : - Transmission d'un lead - Transmission d'un contact direct - Transmission d'une nouvelle candidature Mettre en place un webhook ########################## Si vous avez déjà accès à notre API, il vous suffit d'envoyer une requête POST à l'adresse https://www.aladom.fr/restapi/utils/webhooks/setup/ en précisant l'URL sur laquelle vous voulez que les données vous soient envoyées. Vous recevrez en réponse le token d'authentification que nous utiliserons dans les requêtes que nous vous enverrons. Si vous n'avez pas encore accès à notre API, contactez nous pour en demander un. .. code-block:: bash $ token='fedcba9876543210deadbeef0123456789abcdef' $ curl -H"Authorization: Token $token" -XPOST -d 'url=https://exemple.fr/aladom.wh' https://www.aladom.fr/restapi/utils/webhooks/setup/ .. code-block:: json { "token": "A486kFbdNn1rvesf79ulN+ozyqPUqeRxc2aMBBwT", "url": "https://exemple.fr/aladom.wh" } Une fois cette opération effectuée, les événements vous serons envoyés par requête ``POST`` avec le header ``Authorization: Token A486kFbdNn1rvesf79ulN+ozyqPUqeRxc2aMBBwT`` Un header ``X-User-ID`` contenant l'ID de l'utilisateur concerné sera aussi envoyé. Si vous gerez un réseau, ce header vous permettra de différencier les agences. Pour obtenir la liste des utilisateurs que vous gérez, vous pouvez envoyer une requête ``GET`` à l'adresse https://www.aladom.fr/restapi/user/member/my/managed_users/ Nous vous enverrons également un headers ``X-Webhook-Version`` contenant un numéro de version qui changera à chaque fois que des modifications seront apportées à nos webhooks. Notez cependant que chaque nouvelle version sera rétro-compatible, seuls des champs seront ajoutés. Traiter les requêtes reçues ########################### Les requêtes vous seront envoyées au format JSON. Le JSON envoyé comportera toujours la clé ``event``. Comme tous les événements sont envoyés à la même adresse, cette clé permet de vous indiquer de quel type d'événement il s'agit. C'est la première chose à vérifier, pour brancher correctement la suite du traitement. Si la valeur de ``event`` ne fait pas partie des valeurs que vous supportez, renvoyez un status code ``501`` (Not Implemented). Pour les autres clés, référez-vous à la documentation de chaque événement. Dans la plupart des cas, un objet vous sera envoyé dans la clé ``object`` et le type de cet objet sera précisé par la clé ``type``. Si nous recevons une réponse ``500``, ``502``, ``503``, ``504`` ou si une erreur de connexion survient, nous ferons jusqu'à 4 nouvelles tentatives avant d'abandonner définitivement. D'autre part, si votre serveur ne répond pas en moins de 10 secondes, la requête sera abandonnée. Sauf cas particulier précisé dans la documentation de chaque événement, nous n'attendons aucune réponse particulière et toutes les réponses autres que ``500``, ``502``, ``503`` et ``504`` seront traitées de la même manières. Cependant, nous loggons le status code reçu pour chaque requête envoyée. Pour débugger d'éventuels problèmes, il est donc préférable de retourner un code HTTP pertinent. Exemple d'implémentation en Python ################################## .. code-block:: python import json import warnings from django.views.generic import View class AladomWebhookView(View): def get(self, request, *args, **kwargs): authorization = request.META.get('HTTP_AUTHORIZATION') if authorization != 'Token {}'.format(settings.ALADOM_WEBHOOK_TOKEN) response = HttpResponse(status=401) response['WWW-Authenticate'] = 'Token realm="aladom webhook"' return response version = request.META.get('HTTP_X_WEBHOOK_VERSION') if version != settings.ALADOM_WEBHOOK_VERSION: warnings.warn( "New Aladom Webhook version available : {}".format(version) ) event = json.loads(request.body.decode('utf-8')) if event['event'] == 'new_lead': handler = self.handle_new_lead elif event['event'] == 'new_candidacy': handler = self.handle_new_candidacy elif event['event'] == 'new_contact_quote': handler = self.handle_new_contact_quote else: warnings.warn("Unknown Aladom event: {}".format(event['event'])) return HttpResponse(status=501) response = handler(event) if response: return response return HttpResponse(status=204) def handle_new_lead(self, event): if event['type'] == 'quotation.CleaningQuoteRequest': handler = self.record_cleaning_lead elif event['type'] == 'quotation.HomeFurnishingsQuoteRequest': handler = self.record_home_furnishings_lead elif event['type'] == 'quotation.CraftsQuoteRequest': handler = self.record_crafts_lead elif event['type'] == 'quotation.HelpQuoteRequest': handler = self.record_help_lead elif event['type'] == 'quotation.ChildcareQuoteRequest': handler = self.record_childcare_lead elif event['type'] == 'quotation.MusicQuoteRequest': handler = self.record_music_lead elif event['type'] == 'quotation.TutoringQuoteRequest': handler = self.record_tutoring_lead else: warnings.warn("Unknown Aladom lead type: {}".format(event['type'])) return HttpResponse(status=501) return handler(event['object']) def handle_new_candidacy(self, event): pass def handle_new_contact_quote(self, event): pass def record_cleaning_lead(self, lead): pass def record_home_furnishings_lead(self, lead): pass def record_crafts_lead(self, lead): pass def record_help_lead(self, lead): pass def record_childcare_lead(self, lead): pass def record_music_lead(self, lead): pass def record_tutoring_lead(self, lead): pass Modifier les informations du webhook #################################### Si vous avez perdu le token d'authentification, s'il a été corrompu ou si l'URL du webhook a changé, vous pouvez rappeler le même endpoint pour nous communiquer la nouvelle URL et régénérer le token. .. code-block:: bash $ token='fedcba9876543210deadbeef0123456789abcdef' $ curl -H"Authorization: Token $token" -XPOST -d 'url=https://www.exemple.com/aladom.wh' https://www.aladom.fr/restapi/utils/webhooks/setup/ .. code-block:: json { "token": "1CtBRWjgmBeQmPipqk8jj/5H0mFDP272MMz1TDme", "old_url": "https://exemple.fr/aladom.wh", "url": "https://www.exemple.com/aladom.wh" }