Informații generale utilizare API¶
URL-ul pentru API este: https://{MEDIU}.keez.ro/api/v1.0/public-api
De observat proprietatea {MEDIU} din URL, aceasta se va înlocui cu MEDIU pentru care se face apelul:
Pentru Staging: https://staging.keez.ro/api/v1.0/public-api
Pentru Producție: https://app.keez.ro/api/v1.0/public-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
sunt parametrii care apar și ei in URL dar nu fac parte din endpoint și sunt de tip «cheie-valoare»
Exemplu: https://staging.keez.ro/api/v1.0/public-api/items?clientEid=Test123
Se poate observa poziția parametrului {clientEid} după caracterul «?»
- RequestBody
sunt parametrii care nu apar în URL deloc, ci sunt serviți ca request body al apelului
Se observă că nu există nici un parametrul în url, dar apelul arată de forma
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
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ă
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)