L'API REST de Mayan EDMS expose l'ensemble des fonctionnalités de l'application via des endpoints HTTP standardisés. Ce guide présente les opérations les plus courantes avec des exemples concrets en curl et Python.
Base URL : https://mayan.example.com/api/v4/
Documentation interactive : https://mayan.example.com/api/swagger/
Schéma OpenAPI : https://mayan.example.com/api/swagger.json
curl -X POST https://mayan.example.com/api/v4/auth/token/obtain/ \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "motdepasse"}'
{"token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"}
import requests
BASE_URL = "https://mayan.example.com/api/v4"
response = requests.post(f"{BASE_URL}/auth/token/obtain/", json={
"username": "admin",
"password": "motdepasse"
})
TOKEN = response.json()["token"]
HEADERS = {"Authorization": f"Token {TOKEN}"}
Toutes les requêtes suivantes incluent le header
Authorization: Token {token}.
curl -H "Authorization: Token $TOKEN" \
"https://mayan.example.com/api/v4/documents/?page=1&page_size=20"
docs = requests.get(f"{BASE_URL}/documents/", headers=HEADERS, params={
"page": 1,
"page_size": 20
}).json()
for doc in docs["results"]:
print(doc["id"], doc["label"])
Réponse clés : id, uuid, label, description, language, document_type, datetime_created, file_latest, version_active
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/documents/42/
doc = requests.get(f"{BASE_URL}/documents/42/", headers=HEADERS).json()
print(doc["label"], doc["datetime_created"])
curl -X POST https://mayan.example.com/api/v4/documents/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"document_type_id": 1, "label": "Contrat 2026", "description": "Contrat annuel"}'
doc = requests.post(f"{BASE_URL}/documents/", headers=HEADERS, json={
"document_type_id": 1,
"label": "Contrat 2026",
"description": "Contrat annuel"
}).json()
doc_id = doc["id"]
curl -X POST https://mayan.example.com/api/v4/documents/upload/ \
-H "Authorization: Token $TOKEN" \
-F "file=@/chemin/vers/contrat.pdf" \
-F "document_type_id=1" \
-F "label=Contrat Dupont 2026"
with open("/chemin/vers/contrat.pdf", "rb") as f:
response = requests.post(
f"{BASE_URL}/documents/upload/",
headers=HEADERS,
data={"document_type_id": 1, "label": "Contrat Dupont 2026"},
files={"file": ("contrat.pdf", f, "application/pdf")}
)
doc = response.json()
doc_id = doc["id"]
print(f"Document créé : id={doc_id}")
Retourne 202 Accepted — le traitement (génération des pages, OCR, indexation) est asynchrone. Le document est disponible immédiatement mais ses pages peuvent ne pas l'être encore.
curl -X PATCH https://mayan.example.com/api/v4/documents/42/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"label": "Contrat Dupont 2026 — révisé", "description": "Version finale"}'
requests.patch(f"{BASE_URL}/documents/42/", headers=HEADERS, json={
"label": "Contrat Dupont 2026 — révisé"
})
curl -X DELETE https://mayan.example.com/api/v4/documents/42/ \
-H "Authorization: Token $TOKEN"
requests.delete(f"{BASE_URL}/documents/42/", headers=HEADERS)
# Retourne 202 — asynchrone
curl -X POST https://mayan.example.com/api/v4/documents/42/type/change/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"document_type_id": 3}'
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/documents/42/files/
files = requests.get(f"{BASE_URL}/documents/42/files/", headers=HEADERS).json()
for f in files["results"]:
print(f["id"], f["filename"], f["size"], f["mimetype"])
curl -X POST https://mayan.example.com/api/v4/documents/42/files/ \
-H "Authorization: Token $TOKEN" \
-F "file_new=@/chemin/vers/nouvelle_version.pdf" \
-F "action_name=replace" \
-F "comment=Version corrigée"
with open("nouvelle_version.pdf", "rb") as f:
requests.post(
f"{BASE_URL}/documents/42/files/",
headers=HEADERS,
data={"action_name": "replace", "comment": "Version corrigée"},
files={"file_new": ("nouvelle_version.pdf", f, "application/pdf")}
)
curl -H "Authorization: Token $TOKEN" \
"https://mayan.example.com/api/v4/documents/42/files/1/pages/1/image/" \
--output page1.png
img = requests.get(
f"{BASE_URL}/documents/42/files/1/pages/1/image/",
headers=HEADERS
)
with open("page1.png", "wb") as f:
f.write(img.content)
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/metadata_types/
types = requests.get(f"{BASE_URL}/metadata_types/", headers=HEADERS).json()
for t in types["results"]:
print(t["id"], t["name"], t["label"])
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/documents/42/metadata/
metas = requests.get(f"{BASE_URL}/documents/42/metadata/", headers=HEADERS).json()
for m in metas["results"]:
print(m["metadata_type"]["name"], "=", m["value"])
curl -X POST https://mayan.example.com/api/v4/documents/42/metadata/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"metadata_type_id": 3, "value": "2026-05-01"}'
requests.post(f"{BASE_URL}/documents/42/metadata/", headers=HEADERS, json={
"metadata_type_id": 3,
"value": "2026-05-01"
})
curl -X PATCH https://mayan.example.com/api/v4/documents/42/metadata/7/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": "2026-12-31"}'
requests.patch(f"{BASE_URL}/documents/42/metadata/7/", headers=HEADERS, json={
"value": "2026-12-31"
})
curl -X DELETE https://mayan.example.com/api/v4/documents/42/metadata/7/ \
-H "Authorization: Token $TOKEN"
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/tags/
tags = requests.get(f"{BASE_URL}/tags/", headers=HEADERS).json()
for t in tags["results"]:
print(t["id"], t["label"], t["color"])
curl -X POST https://mayan.example.com/api/v4/tags/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"label": "Urgent", "color": "#FF0000"}'
tag = requests.post(f"{BASE_URL}/tags/", headers=HEADERS, json={
"label": "Urgent",
"color": "#FF0000"
}).json()
tag_id = tag["id"]
curl -X POST https://mayan.example.com/api/v4/documents/42/tags/attach/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"tag": 5}'
requests.post(f"{BASE_URL}/documents/42/tags/attach/", headers=HEADERS, json={
"tag": 5
})
curl -X POST https://mayan.example.com/api/v4/documents/42/tags/remove/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"tag": 5}'
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/documents/42/tags/
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/cabinets/
cabinets = requests.get(f"{BASE_URL}/cabinets/", headers=HEADERS).json()
for c in cabinets["results"]:
print(c["id"], c["label"], c["full_path"])
# Cabinet racine
curl -X POST https://mayan.example.com/api/v4/cabinets/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"label": "Ressources Humaines"}'
# Sous-cabinet
curl -X POST https://mayan.example.com/api/v4/cabinets/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"label": "Contrats", "parent_id": 10}'
# Créer un sous-cabinet
cabinet = requests.post(f"{BASE_URL}/cabinets/", headers=HEADERS, json={
"label": "Contrats 2026",
"parent_id": 10
}).json()
cabinet_id = cabinet["id"]
curl -X POST https://mayan.example.com/api/v4/cabinets/10/documents/add/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"document": 42}'
requests.post(f"{BASE_URL}/cabinets/10/documents/add/", headers=HEADERS, json={
"document": 42
})
curl -X POST https://mayan.example.com/api/v4/cabinets/10/documents/remove/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"document": 42}'
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/documents/42/cabinets/
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/documents/42/workflow_instances/
instances = requests.get(
f"{BASE_URL}/documents/42/workflow_instances/",
headers=HEADERS
).json()
for wi in instances["results"]:
print(wi["id"], wi["workflow_template"]["label"], wi["current_state"]["label"])
# 1. Voir les transitions disponibles
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/documents/42/workflow_instances/1/log_entries/transitions/
# 2. Effectuer la transition
curl -X POST \
https://mayan.example.com/api/v4/documents/42/workflow_instances/1/log_entries/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"workflow_template_transition_id": 3, "comment": "Approuvé par direction"}'
# Lister les transitions disponibles
transitions = requests.get(
f"{BASE_URL}/documents/42/workflow_instances/1/log_entries/transitions/",
headers=HEADERS
).json()
# Effectuer la première transition disponible
transition_id = transitions["results"][0]["id"]
requests.post(
f"{BASE_URL}/documents/42/workflow_instances/1/log_entries/",
headers=HEADERS,
json={
"workflow_template_transition_id": transition_id,
"comment": "Traitement automatique via API"
}
)
curl -X POST \
https://mayan.example.com/api/v4/documents/42/workflow_instances/launch/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"workflow_template_id": 2}'
# Recherche dans tous les champs
curl -H "Authorization: Token $TOKEN" \
"https://mayan.example.com/api/v4/search/documents.document/?q=contrat+dupont"
results = requests.get(
f"{BASE_URL}/search/documents.document/",
headers=HEADERS,
params={"q": "contrat dupont"}
).json()
for doc in results["results"]:
print(doc["id"], doc["label"])
# Recherche sur le label ET le type de document (AND)
curl -H "Authorization: Token $TOKEN" \
"https://mayan.example.com/api/v4/search/advanced/documents.document/?label=contrat&document_type__label=Facture&_match_all=yes"
results = requests.get(
f"{BASE_URL}/search/advanced/documents.document/",
headers=HEADERS,
params={
"label": "contrat",
"document_type__label": "Facture",
"_match_all": "yes"
}
).json()
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/search_models/
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/document_types/
doc_types = requests.get(f"{BASE_URL}/document_types/", headers=HEADERS).json()
for dt in doc_types["results"]:
print(dt["id"], dt["label"])
curl -H "Authorization: Token $TOKEN" \
https://mayan.example.com/api/v4/document_types/1/metadata_types/
L'endpoint batch permet d'exécuter plusieurs requêtes API en un seul appel HTTP.
curl -X POST https://mayan.example.com/api/v4/batch_requests/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '[
{"method": "GET", "url": "/api/v4/documents/42/"},
{"method": "GET", "url": "/api/v4/documents/42/metadata/"},
{"method": "GET", "url": "/api/v4/documents/42/tags/"}
]'
responses = requests.post(f"{BASE_URL}/batch_requests/", headers=HEADERS, json=[
{"method": "GET", "url": "/api/v4/documents/42/"},
{"method": "GET", "url": "/api/v4/documents/42/metadata/"},
{"method": "GET", "url": "/api/v4/documents/42/tags/"},
]).json()
doc_data = responses[0]
metadata = responses[1]
tags = responses[2]
Utile pour réduire la latence réseau lors de chargements de pages ou de rapports.
Tous les endpoints de liste sont paginés.
curl -H "Authorization: Token $TOKEN" \
"https://mayan.example.com/api/v4/documents/?page=2&page_size=50"
def get_all(url, headers, params=None):
"""Itère sur toutes les pages d'un endpoint."""
params = params or {}
params["page_size"] = 100
results = []
page = 1
while True:
params["page"] = page
r = requests.get(url, headers=headers, params=params).json()
results.extend(r["results"])
if not r["next"]:
break
page += 1
return results
all_docs = get_all(f"{BASE_URL}/documents/", HEADERS)
Champs de réponse pagination :
count — nombre total de résultatsnext — URL de la page suivante (null si dernière page)previous — URL de la page précédenteresults — liste des objets# Filtrer par label exact
curl -H "Authorization: Token $TOKEN" \
"https://mayan.example.com/api/v4/tags/?label=Urgent"
# Tri décroissant par date
curl -H "Authorization: Token $TOKEN" \
"https://mayan.example.com/api/v4/documents/?ordering=-datetime_created"
# Sélectionner uniquement certains champs
curl -H "Authorization: Token $TOKEN" \
"https://mayan.example.com/api/v4/documents/?_fields_only=id,label,datetime_created"
| Code | Signification |
|---|---|
200 OK |
Succès (GET, PATCH, PUT) |
201 Created |
Ressource créée (POST) |
202 Accepted |
Opération asynchrone lancée (upload, delete) |
204 No Content |
Succès sans contenu retourné (DELETE) |
400 Bad Request |
Paramètres invalides — voir le corps de la réponse |
401 Unauthorized |
Token manquant ou invalide |
403 Forbidden |
Permission insuffisante |
404 Not Found |
Ressource inexistante |
500 Internal Server Error |
Erreur serveur |
import requests
from typing import Optional
class MayanClient:
def __init__(self, base_url: str, username: str, password: str):
self.base_url = base_url.rstrip("/")
r = requests.post(f"{self.base_url}/api/v4/auth/token/obtain/", json={
"username": username, "password": password
})
r.raise_for_status()
self.headers = {"Authorization": f"Token {r.json()['token']}"}
def _url(self, path):
return f"{self.base_url}/api/v4/{path.lstrip('/')}"
def get(self, path, **kwargs):
return requests.get(self._url(path), headers=self.headers, **kwargs)
def post(self, path, **kwargs):
return requests.post(self._url(path), headers=self.headers, **kwargs)
def patch(self, path, **kwargs):
return requests.patch(self._url(path), headers=self.headers, **kwargs)
def delete(self, path, **kwargs):
return requests.delete(self._url(path), headers=self.headers, **kwargs)
def upload_document(self, filepath: str, document_type_id: int,
label: Optional[str] = None) -> dict:
with open(filepath, "rb") as f:
data = {"document_type_id": document_type_id}
if label:
data["label"] = label
r = self.post("documents/upload/",
data=data,
files={"file": (filepath, f)})
r.raise_for_status()
return r.json()
def add_metadata(self, doc_id: int, metadata_type_id: int, value: str):
r = self.post(f"documents/{doc_id}/metadata/",
json={"metadata_type_id": metadata_type_id, "value": value})
r.raise_for_status()
return r.json()
def attach_tag(self, doc_id: int, tag_id: int):
r = self.post(f"documents/{doc_id}/tags/attach/", json={"tag": tag_id})
r.raise_for_status()
def add_to_cabinet(self, cabinet_id: int, doc_id: int):
r = self.post(f"cabinets/{cabinet_id}/documents/add/",
json={"document": doc_id})
r.raise_for_status()
def search(self, query: str, model: str = "documents.document") -> list:
r = self.get(f"search/{model}/", params={"q": query})
r.raise_for_status()
return r.json()["results"]
def all_pages(self, path, params=None):
params = dict(params or {})
params["page_size"] = 100
results, page = [], 1
while True:
params["page"] = page
r = self.get(path, params=params)
data = r.json()
results.extend(data["results"])
if not data["next"]:
break
page += 1
return results
# Utilisation
client = MayanClient("https://mayan.example.com", "admin", "motdepasse")
# Upload + métadonnée + tag + cabinet en une séquence
doc = client.upload_document("contrat.pdf", document_type_id=1, label="Contrat Dupont")
doc_id = doc["id"]
client.add_metadata(doc_id, metadata_type_id=3, value="2026-05-01")
client.attach_tag(doc_id, tag_id=5)
client.add_to_cabinet(cabinet_id=10, doc_id=doc_id)
# Recherche
results = client.search("Dupont contrat 2026")
for r in results:
print(r["id"], r["label"])
# Pour chaque facture générée par l'ERP
for facture in erp.get_nouvelles_factures():
doc = client.upload_document(
filepath=facture.pdf_path,
document_type_id=DOC_TYPE_FACTURE,
label=f"Facture {facture.numero}"
)
client.add_metadata(doc["id"], META_NUMERO_FACTURE, facture.numero)
client.add_metadata(doc["id"], META_DATE_FACTURE, facture.date.isoformat())
client.add_metadata(doc["id"], META_MONTANT, str(facture.montant))
client.add_to_cabinet(CABINET_FACTURES_2026, doc["id"])
instances = client.get(f"documents/{doc_id}/workflow_instances/").json()["results"]
for instance in instances:
if instance["workflow_template"]["label"] == "Validation contrat":
current_state = instance["current_state"]["label"]
if current_state == "En attente de validation":
# Récupérer les transitions disponibles
transitions = client.get(
f"documents/{doc_id}/workflow_instances/{instance['id']}/log_entries/transitions/"
).json()["results"]
# Déclencher "Approuver"
for t in transitions:
if t["label"] == "Approuver":
client.post(
f"documents/{doc_id}/workflow_instances/{instance['id']}/log_entries/",
json={"workflow_template_transition_id": t["id"],
"comment": "Approuvé automatiquement"}
)
docs = client.all_pages("documents/")
rapport = []
for doc in docs:
metas = client.get(f"documents/{doc['id']}/metadata/").json()["results"]
rapport.append({
"id": doc["id"],
"label": doc["label"],
"date": doc["datetime_created"],
**{m["metadata_type"]["name"]: m["value"] for m in metas}
})
import csv
with open("rapport.csv", "w") as f:
writer = csv.DictWriter(f, fieldnames=rapport[0].keys())
writer.writeheader()
writer.writerows(rapport)
_fields_only pour ne récupérer que les champs nécessaires — réduit la taille des réponsespage_size=100 (maximum) pour réduire le nombre d'appels