Informații generale utilizare API

Atenționare

Regulile despre mediile disponibile și anume staging și app au fost descrise la Vezi Autorizare - Secțiunea Medii

  • Utilizarea API-ului se face cu ajutorul următoarelor operații, numite și verbe:

    • GET - este folosit pentru a aduce informații despre un anumit model de date

    • POST - este folosit pentru a adăuga informații în API

    • PUT - este folosit pentru a modifica informații într-un anumit model de date

    • PATCH - este folosit pentru a modifica doar anumite informații dintr-un model de date

    • DELETE - este folosit pentru a șterge din API

Parametri path/request

  • Anumite operații pot avea sau nu parametrii. Aceștia pot fi de mai multe tipuri și se disting prin poziția în cadrul URL-ului sau a felului cum arată.

    • PathVariable
      • sunt parametrii care fac parte din endpoint și sunt de tip «valoare»

      • Exemplu: https://staging.keez.ro/api/v1.0/public-api/{clientEid}/items

      • Se poate observa poziția parametrului {clientEid} în interiorul endpoint-ului

    • RequestParam
    • RequestBody
      data = {
          'clientEid': Test123
      }
      requests.post(url=URL, headers={'Content-Type': 'application/json', 'Authorization': token}, json=data)
      

      Atenționare

      Parametrii de tip RequestBody pot fi serviți doar la operațiile de POST/PUT/PATCH/DELETE. Verbul GET nu suportă parametrii RequestBody

Coduri de eroare posibile

Cod

Denumire

Descriere

Optiuni

200

OK

Totul a mers perfect

Folosiți datele returnate dacă acestea există

400

Bad Request

Format de date/apel incorect

Modelul de date din parametrii sau din corpul apelului sunt greșit formatâte

403

Forbidden

Nu ai permisiune pentru a accesa acea funcționalitate

Ai uitat să apelezi preluarea de token / Nu ai folosit token-ul în apelul către API /
Trebuie să contactezi Keez.ro

401

Unauthorized

Nu ai permisiune pentru a accesa acea funcționalitate

Ai uitat să apelezi preluarea de token / Nu ai folosit token-ul în apelul către API

404

Not Found

Pagina apelată către API nu există

Verifică URL-ul din apel

500

Internal Server Error

Probleme la server

Contactează Keez.ro

502

Bad Gateway

Aplicația nu funcționează, probleme rețea

Așteptați câteva minute și apoi reîncercați

504

Gateway Timeout

Timpul de asteptare a fost depasit

Așteptați câteva minute și apoi reîncercați

503

Service Unavailable

Aplicația nu funcționează

Așteptați câteva minute și apoi reîncercați

Model de eroare

{
  'Code': '401',
  'Message': 'Nu aveti drepturi suficiente pentru accesarea acestei functionalitati.',
  'Id': 'f1c6b834d26141fbad0282f165a015ae',
  'statusCode': 'UNAUTHORIZED'
}

Notă

Pentru mai multe informații despre modelul de eroare Vezi Model de Eroare

Identificatorul Keez

Orice model de date conține o proprietate prin care se poate identifica în mod unic acea resursă (factură, articol, categorie, partener). În majoritatea cazurilor aceasta este denumită externalId, iar în manual aceasta se regaseste sub denumirea de Identificator Keez

Reguli generale

  • Orice operație de tip GET va returna un rezultat de tip model de date sau un identificator externalId. Vezi Modele de Date disponibile

  • Orice operație de DELETE nu va returna nimic la finalul operației

  • Orice operație de PATCH nu va returna nimic la finalul operației

  • Orice operație de PUT nu va returna nimic la finalul operației

  • Orice operație de PATCH nu va returna nimic la finalul operației

Atenționare

Indiferent dacă se creează un articol sau o factură, identificatorul Keez al acestuia externalId trebuie furnizat pentru a avea acces ulterior la el.

Notă

Exemplul 1: Dacă se creează un articol cu scopul de a fi folosit pe o factură, identificatorul Keez externalId trebuie furnizat în corpul apelului, | deoarece operația de PUT/POST pentru crearea articolului nu returnează identificatorul Keez externalId

Notă

Exemplul 2: Dacă se creează o factură cu scopul de a fi trimisă pe email, identificatorul Keez externalId trebuie furnizat în corpul apelului,
deoarece operația de PUT/POST pentru crearea facturii nu returnează identificatorul Keez externalId

Wrapper Python peste API

  • Keez.ro oferă o clasă wrapper peste acest API, scris în Python

import time
from urllib.parse import urlencode

import python_jwt as jwt
import requests


class KeezPublicApi:
    def __init__(self, application_id=None, secret=None, token_url=None, api_url=None):
        self.expires = time.gmtime(0)

        if application_id is None:
            raise Exception('application_id not provided!')
        if secret is None:
            raise Exception('secret not provided!')
        if token_url is None:
            raise Exception('token_url not provided!')
        if api_url is None:
            raise Exception('api_url not provided!')

        self.baseAPIUrl = api_url
        self.token_url = token_url
        self.application_id = application_id
        self.secret = secret

        self.token = None
        self.token_type = None
        self.access_token = None

    def refresh_auth_token(self):
        if self.token_expired():
            self.generate_token()

    def token_expired(self):
        return self.expires < time.gmtime()

    def generate_token(self):
        req = requests.post(self.token_url, data={
            'client_id': f'app{self.application_id}',
            'client_secret': self.secret,
            'grant_type': 'client_credentials',
            'scope': 'public-api',
        }, headers={
            'Content-Type': 'application/x-www-form-urlencoded'
        })
        req.raise_for_status()
        json = req.json()

        self.token = f'{json["token_type"]} {json["access_token"]}'
        self.token_type = json['token_type']
        self.access_token = json['access_token']
        (_, claims) = jwt.process_jwt(self.access_token)
        self.expires = time.gmtime(claims['exp'])

    def api_call(self, verb, url, data=None):
        self.refresh_auth_token()

        _req = None

        if verb == 'GET':
            _req = requests.get(url=self.baseAPIUrl + url, headers={'Content-Type': 'application/json',
                                                                    'Authorization': self.token}, json=data)
        elif verb == 'POST':
            _req = requests.post(url=self.baseAPIUrl + url, headers={'Content-Type': 'application/json',
                                                                     'Authorization': self.token}, json=data)
        elif verb == 'PUT':
            _req = requests.put(url=self.baseAPIUrl + url, headers={'Content-Type': 'application/json',
                                                                    'Authorization': self.token}, json=data)
        elif verb == 'PATCH':
            _req = requests.patch(url=self.baseAPIUrl + url, headers={'Content-Type': 'application/json',
                                                                      'Authorization': self.token}, json=data)
        elif verb == 'DELETE':
            _req = requests.delete(url=self.baseAPIUrl + url, headers={'Content-Type': 'application/json',
                                                                       'Authorization': self.token}, json=data)

        if _req is None:
            raise Exception(f'Internal error: invalid verb {verb}')

        _req.raise_for_status()
        return _req.json()

    """ Public call methods (ITEMS)"""

    def items(self, client_eid, filter=None, order=None, count=None, offset=None):
        url = f'/{client_eid}/items'

        params = {}

        if filter is not None:
            params.update({'filter': f' AND '.join(f'{x}:{filter[x]}' for x in filter)})
        if order is not None:
            params.update({'order': f' AND '.join(f'{x} {order[x]}' for x in order)})
        if count is not None:
            params.update({'count': str(count)})
        if offset is not None:
            params.update({'offset': str(offset)})

        qstr = urlencode(params)
        url = url + '?' + qstr

        return self.api_call('GET', url)

    def item(self, client_eid, item_external_id):
        return self.api_call('GET', f'/{client_eid}/items/{item_external_id}')

    def create_item(self, client_eid, data):
        return self.api_call('POST', f'/{client_eid}/items', data=data)

    def replace_item(self, client_eid, item_external_id, data):
        return self.api_call('PUT', f'/{client_eid}/items/{item_external_id}', data=data)

    def update_item(self, client_eid, item_external_id, data):
        return self.api_call('PATCH', f'/{client_eid}/items/{item_external_id}', data=data)

    """ Public call methods (INVOICES)"""

    def invoices(self, client_eid, filter=None, order=None, count=None, offset=None):
        url = f'/{client_eid}/invoices'

        params = {}

        if filter is not None:
            params.update({'filter': f' AND '.join(f'{x}:{filter[x]}' for x in filter)})
        if order is not None:
            params.update({'order': f' AND '.join(f'{x} {order[x]}' for x in order)})
        if count is not None:
            params.update({'count': str(count)})
        if offset is not None:
            params.update({'offset': str(offset)})

        qstr = urlencode(params)
        url = url + '?' + qstr

        return self.api_call('GET', url)

    def invoice(self, client_eid, invoice_external_id):
        return self.api_call('GET', f'/{client_eid}/invoices/{invoice_external_id}')

    def create_invoice(self, client_eid, data):
        return self.api_call('POST', f'/{client_eid}/invoices', data=data)

    def replace_invoice(self, client_eid, invoice_external_id, data):
        return self.api_call('PUT', f'/{client_eid}/invoices/{invoice_external_id}', data=data)

    def delete_invoice(self, client_eid, invoice_eid):
        return self.api_call('DELETE', f'/{client_eid}/invoices', data={
            'externalId': invoice_eid
        })

    def validate_invoice(self, client_eid, invoice_eid):
        return self.api_call('POST', f'/{client_eid}/invoices/valid', data={
            'externalId': invoice_eid
        })

    def submit_e_factura(self, client_eid, invoice_eid):
        return self.api_call('POST', f'/{client_eid}/invoices/efactura/submitted', data={
            'externalId': invoice_eid
        })

    def cancel_invoice(self, client_eid, invoice_eid):
        return self.api_call('POST', f'/{client_eid}/invoices/canceled', data={
            'externalId': invoice_eid
        })

    def deliver_invoice_by_email(self, invoice_eid, to=None, cc=None, bcc=None):
        return self.api_call('POST', f'/invoices/delivery', data={
            'invoiceExternalId': invoice_eid,
            'info': [
                {
                    'deliveryMethod': 'Email',
                    'representationType': 'Attachment',
                    'recipients': {
                        'to': to,
                        'cc': cc,
                        'bcc': bcc
                    }
                }
            ]
        })

    def invoice_pdf(self, client_eid, invoice_external_id):
        self.refresh_auth_token()

        _req = requests.get(url=self.baseAPIUrl + f'/{client_eid}/invoices/{invoice_external_id}/pdf',
                            headers={'Authorization': self.token})
        if _req is None:
            raise Exception(f'Internal error: invalid download!')

        disp = _req.headers['content-disposition']
        name = None
        if 'filename' in disp:
            name = disp[disp.index('=') + 1:].strip()
            if '"' in name:
                name = name.replace('"', '').strip()

        return name, _req.content

    def measure_unit(self):
        return self.api_call('GET', f'/items/measure-unit')

    def invoice_payment_type(self):
        return self.api_call('GET', f'/invoices/payment-type')

Mod de utilizare wrapper

token_url = 'https://staging.keez.ro/idp/connect/token'
api_url = f'https://staging.keez.ro/api/v1.0/public-api'

client_eid = '{RECEIVED_CLIENT_EID}'
application_id = '{RECEIVED_APPLICATION_ID}'
secret = '{RECEIVED_SECRET}'

# Se face 'legatura' la wrapper pe baza proprietăților necesare (application_id, secret, token_url, api_url)
wrapper_service = KeezPublicApi(application_id=application_id, secret=secret, token_url=token_url, api_url=api_url)

# Se apelează metodele din interiorul wrapper-ului pe baza client_eid si a altor parametri, in functie de metodă
result = wrapper_service.items(client_eid=client_eid)

# Se printează rezultatul
print(result)