Skip to content

GlpiAPI

Synchronous client for the GLPI 11 legacy REST API (/apirest.php).

glpi_utils.api.GlpiAPI

Synchronous client for the GLPI 11 legacy REST API (/apirest.php).

Parameters:

Name Type Description Default
url str or None

Base URL of the GLPI server, e.g. "https://glpi.example.com". Can be supplied via the GLPI_URL environment variable.

None
app_token str or None

Application token configured in Setup → General → API. Optional but recommended for production use. Can be supplied via GLPI_APP_TOKEN.

None
verify_ssl bool

Verify the server's TLS certificate (default: True).

True
timeout int

HTTP request timeout in seconds (default: 30).

30

Examples:

Authenticate with username / password::

from glpi_utils import GlpiAPI

api = GlpiAPI(url="https://glpi.example.com", app_token="xxxx")
api.login(username="glpi", password="glpi")

# Fetch a single page
tickets = api.ticket.get_all(range="0-9")

# Fetch ALL tickets automatically (auto-pagination)
all_tickets = api.ticket.get_all_pages()

api.logout()

Authenticate with a personal API token::

api = GlpiAPI(url="https://glpi.example.com")
api.login(user_token="q56hqkniwot8wntb3z1qarka5atf365taaa2uyjrn")

As a context manager::

with GlpiAPI(url="https://glpi.example.com") as api:
    api.login(username="glpi", password="glpi")
    print(api.version)
Source code in glpi_utils/api.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
class GlpiAPI:
    """Synchronous client for the GLPI 11 legacy REST API (``/apirest.php``).

    Parameters
    ----------
    url : str or None
        Base URL of the GLPI server, e.g. ``"https://glpi.example.com"``.
        Can be supplied via the ``GLPI_URL`` environment variable.
    app_token : str or None
        Application token configured in *Setup → General → API*.
        Optional but recommended for production use.
        Can be supplied via ``GLPI_APP_TOKEN``.
    verify_ssl : bool
        Verify the server's TLS certificate (default: ``True``).
    timeout : int
        HTTP request timeout in seconds (default: ``30``).

    Examples
    --------
    Authenticate with username / password::

        from glpi_utils import GlpiAPI

        api = GlpiAPI(url="https://glpi.example.com", app_token="xxxx")
        api.login(username="glpi", password="glpi")

        # Fetch a single page
        tickets = api.ticket.get_all(range="0-9")

        # Fetch ALL tickets automatically (auto-pagination)
        all_tickets = api.ticket.get_all_pages()

        api.logout()

    Authenticate with a personal API token::

        api = GlpiAPI(url="https://glpi.example.com")
        api.login(user_token="q56hqkniwot8wntb3z1qarka5atf365taaa2uyjrn")

    As a context manager::

        with GlpiAPI(url="https://glpi.example.com") as api:
            api.login(username="glpi", password="glpi")
            print(api.version)
    """

    def __init__(
        self,
        url: Optional[str] = None,
        app_token: Optional[str] = None,
        verify_ssl: bool = True,
        timeout: int = 30,
    ) -> None:
        self._url = (url or os.environ.get("GLPI_URL", "")).rstrip("/")
        if not self._url:
            raise ValueError(
                "A GLPI URL is required. Pass url= or set GLPI_URL environment variable."
            )
        self._app_token = app_token or os.environ.get("GLPI_APP_TOKEN")
        self._verify_ssl = verify_ssl
        self._timeout = timeout

        self._session_token: Optional[str] = None
        self._http = Session()
        self._version: Optional[GLPIVersion] = None
        self._proxies: dict = {}

    # ------------------------------------------------------------------
    # Context-manager support
    # ------------------------------------------------------------------

    def __enter__(self) -> "GlpiAPI":
        return self

    def __exit__(self, *_: Any) -> None:
        if self._session_token:
            try:
                self.logout()
            except Exception:
                pass

    # ------------------------------------------------------------------
    # Fluent item-type accessors  api.ticket / api.computer / …
    # ------------------------------------------------------------------

    def __getattr__(self, name: str) -> ItemProxy:
        lower = name.lower()
        if lower in _ITEMTYPE_MAP:
            if lower not in self._proxies:
                self._proxies[lower] = ItemProxy(self, _ITEMTYPE_MAP[lower])
            return self._proxies[lower]
        raise AttributeError(
            f"{self.__class__.__name__!r} object has no attribute {name!r}. "
            "Use api.item('YourItemtype') to access non-standard item types."
        )

    def item(self, itemtype: str) -> ItemProxy:
        """Return an :class:`~glpi_utils._resource.ItemProxy` for any itemtype.

        Parameters
        ----------
        itemtype : str
            GLPI itemtype name (case-sensitive), e.g. ``"Ticket"``.
        """
        if itemtype not in self._proxies:
            self._proxies[itemtype] = ItemProxy(self, itemtype)
        return self._proxies[itemtype]

    # ------------------------------------------------------------------
    # Internal HTTP layer
    # ------------------------------------------------------------------

    @property
    def _base_url(self) -> str:
        return f"{self._url}/apirest.php"

    def _default_headers(self) -> dict:
        headers: dict = {"Content-Type": "application/json"}
        if self._app_token:
            headers["App-Token"] = self._app_token
        if self._session_token:
            headers["Session-Token"] = self._session_token
        return headers

    def _request(
        self,
        method: str,
        path: str,
        *,
        headers: Optional[dict] = None,
        params: Optional[dict] = None,
        json: Any = None,
    ) -> Any:
        url = f"{self._base_url}/{path.lstrip('/')}"
        merged = {**self._default_headers(), **(headers or {})}

        log.debug("%s %s  params=%s", method.upper(), url, params)

        try:
            response = self._http.request(
                method, url,
                headers=merged,
                params=params,
                json=json,
                verify=self._verify_ssl,
                timeout=self._timeout,
            )
        except requests.exceptions.ConnectionError as exc:
            raise GlpiConnectionError(f"Cannot reach GLPI at {self._url}: {exc}") from exc
        except requests.exceptions.Timeout as exc:
            raise GlpiConnectionError(f"Request timed out after {self._timeout}s: {exc}") from exc

        log.debug("Response %s from %s", response.status_code, url)

        if response.status_code == 204 or not response.content:
            return None

        _raise_for_glpi_error(response)
        return response.json()

    def _request_with_headers(
        self,
        method: str,
        path: str,
        *,
        params: Optional[dict] = None,
        json: Any = None,
    ) -> tuple:
        """Like ``_request`` but returns ``(body, response_headers)``."""
        url = f"{self._base_url}/{path.lstrip('/')}"
        merged = self._default_headers()

        log.debug("%s %s  params=%s", method.upper(), url, params)

        try:
            response = self._http.request(
                method, url,
                headers=merged,
                params=params,
                json=json,
                verify=self._verify_ssl,
                timeout=self._timeout,
            )
        except requests.exceptions.ConnectionError as exc:
            raise GlpiConnectionError(f"Cannot reach GLPI at {self._url}: {exc}") from exc
        except requests.exceptions.Timeout as exc:
            raise GlpiConnectionError(f"Request timed out after {self._timeout}s: {exc}") from exc

        log.debug("Response %s from %s", response.status_code, url)

        if response.status_code == 204 or not response.content:
            return None, response.headers

        _raise_for_glpi_error(response)
        return response.json(), response.headers

    # ------------------------------------------------------------------
    # Authentication
    # ------------------------------------------------------------------

    def login(
        self,
        username: Optional[str] = None,
        password: Optional[str] = None,
        user_token: Optional[str] = None,
    ) -> None:
        """Authenticate against GLPI and store the session token.

        Parameters
        ----------
        username : str or None
        password : str or None
        user_token : str or None
            Personal API token from the user profile page (*Remote access key*).

        Environment variables
        ---------------------
        ``GLPI_USER``, ``GLPI_PASSWORD``, ``GLPI_USER_TOKEN``
        """
        username   = username   or os.environ.get("GLPI_USER")
        password   = password   or os.environ.get("GLPI_PASSWORD")
        user_token = user_token or os.environ.get("GLPI_USER_TOKEN")

        auth_headers: dict = {}

        if user_token:
            auth_headers["Authorization"] = f"user_token {user_token}"
        elif username and password:
            credentials = b64encode(f"{username}:{password}".encode()).decode()
            auth_headers["Authorization"] = f"Basic {credentials}"
        else:
            raise GlpiAuthError(
                "Provide username+password or user_token (or set "
                "GLPI_USER/GLPI_PASSWORD/GLPI_USER_TOKEN)."
            )

        data = self._request("GET", "initSession", headers=auth_headers)
        self._session_token = data["session_token"]
        log.debug("Session established.")

    def logout(self) -> None:
        """Terminate the active GLPI session."""
        if self._session_token:
            try:
                self._request("GET", "killSession")
            finally:
                self._session_token = None
                log.debug("Session terminated.")

    # ------------------------------------------------------------------
    # Version
    # ------------------------------------------------------------------

    @property
    def version(self) -> GLPIVersion:
        """GLPI server version as a :class:`~glpi_utils.version.GLPIVersion`."""
        if self._version is None:
            try:
                data = self._request("GET", "getGlpiConfig")
                # GLPI 10/11: version is in cfg_glpi.glpi_version
                raw = (
                    (data.get("cfg_glpi") or {}).get("version")
                    or (data.get("cfg_glpi") or {}).get("glpi_version")
                    or data.get("glpi_version")
                    or data.get("version")
                )
            except GlpiAPIError:
                raw = None
            if not raw:
                # Fallback: read from full session
                try:
                    session = self._request("GET", "getFullSession")
                    raw = (
                        (session.get("session") or {}).get("glpi_version")
                        or session.get("glpi_version")
                    )
                except GlpiAPIError:
                    raw = None
            self._version = GLPIVersion(raw or "0.0.0")
        return self._version

    # ------------------------------------------------------------------
    # Session utilities
    # ------------------------------------------------------------------

    def get_my_profiles(self) -> list:
        """Return profiles available to the current user."""
        return self._request("GET", "getMyProfiles")["myprofiles"]

    def get_active_profile(self) -> dict:
        """Return the currently active profile."""
        return self._request("GET", "getActiveProfile")["active_profile"]

    def set_active_profile(self, profile_id: int) -> None:
        """Switch to a different profile."""
        self._request("POST", "changeActiveProfile", json={"profiles_id": profile_id})

    def get_my_entities(self, is_recursive: bool = False) -> list:
        """Return entities accessible to the current user."""
        return self._request(
            "GET", "getMyEntities", params={"is_recursive": int(is_recursive)}
        )["myentities"]

    def get_active_entities(self) -> dict:
        """Return the current active entity context."""
        return self._request("GET", "getActiveEntities")["active_entity"]

    def set_active_entity(self, entity_id: int, is_recursive: bool = False) -> None:
        """Switch the active entity context."""
        self._request(
            "POST",
            "changeActiveEntities",
            json={"entities_id": entity_id, "is_recursive": int(is_recursive)},
        )

    def get_full_session(self) -> dict:
        """Return the full PHP session data."""
        return self._request("GET", "getFullSession")["session"]

    def get_glpi_config(self) -> dict:
        """Return global GLPI configuration."""
        return self._request("GET", "getGlpiConfig")

    # ------------------------------------------------------------------
    # Item CRUD
    # ------------------------------------------------------------------

    def get_item(self, itemtype: str, item_id: int, **kwargs: Any) -> dict:
        """Return a single item by ID.

        Parameters
        ----------
        itemtype : str
        item_id : int
        **kwargs
            Extra query parameters: ``expand_dropdowns``, ``with_logs``,
            ``with_networkports``, ``with_infocoms``, etc.
        """
        params = _boolify_params(kwargs)
        return self._request("GET", f"{itemtype}/{item_id}", params=params)

    def get_all_items(self, itemtype: str, **kwargs: Any) -> list:
        """Return a **single page** of items.

        Use :meth:`get_all_pages` to retrieve every item across all pages
        automatically.

        Parameters
        ----------
        itemtype : str
        **kwargs
            ``range`` (default ``"0-49"``), ``sort``, ``order``,
            ``searchText``, ``is_deleted``, ``expand_dropdowns``, etc.
        """
        params = _boolify_params(kwargs)
        if "range" not in params:
            params["range"] = f"0-{DEFAULT_PAGE_SIZE - 1}"
        return self._request("GET", itemtype, params=params)

    def get_all_pages(
        self,
        itemtype: str,
        page_size: int = DEFAULT_PAGE_SIZE,
        **kwargs: Any,
    ) -> list:
        """Fetch **all** items of *itemtype* by iterating pages automatically.

        GLPI paginates results via a ``range`` query parameter (``0-49``,
        ``50-99``, …) and reports the grand total in the ``Content-Range``
        response header (``0-49/1337``).  This method handles all of that
        transparently and returns a single flat list.

        Parameters
        ----------
        itemtype : str
            GLPI itemtype, e.g. ``"Ticket"``.
        page_size : int
            Items per request (default: 50).  Increase for fewer round-trips
            on large datasets; decrease if responses are slow.
        **kwargs
            Extra GLPI parameters passed to every page request: ``sort``,
            ``order``, ``searchText``, ``is_deleted``,
            ``expand_dropdowns``, etc.  Do **not** pass ``range`` here —
            it is managed internally.

        Returns
        -------
        list
            All matching items as a flat list of dicts.

        Examples
        --------
        ::

            # All open tickets, sorted by date
            tickets = api.get_all_pages(
                "Ticket",
                sort="date_mod",
                order="DESC",
                searchText={"status": "1"},
            )

            # Via the fluent proxy
            computers = api.computer.get_all_pages(expand_dropdowns=True)
        """
        params = _boolify_params(kwargs)
        results: list = []
        start = 0

        while True:
            end = start + page_size - 1
            params["range"] = f"{start}-{end}"

            page_items, resp_headers = self._request_with_headers(
                "GET", itemtype, params=params
            )

            if not page_items:
                break

            results.extend(page_items)

            total = _parse_content_range(
                resp_headers.get("Content-Range", "")
            )

            log.debug(
                "Paginating %s: fetched %d/%s items (range %s-%s)",
                itemtype, len(results), total or "?", start, end,
            )

            # Stop if we know the grand total and have reached it
            if total is not None and len(results) >= total:
                break

            # Stop if GLPI returned fewer items than requested
            # (last partial page — no Content-Range header on some versions)
            if len(page_items) < page_size:
                break

            start += page_size

        return results

    def iter_pages(
        self,
        itemtype: str,
        page_size: int = DEFAULT_PAGE_SIZE,
        **kwargs: Any,
    ) -> Iterator[list]:
        """Yield one page of items at a time.

        Memory-efficient alternative to :meth:`get_all_pages` for very large
        datasets where you want to process each batch immediately rather than
        accumulating everything in RAM.

        Parameters
        ----------
        itemtype : str
        page_size : int
        **kwargs
            Same as :meth:`get_all_pages`.

        Yields
        ------
        list
            One page (list of dicts) per iteration.

        Examples
        --------
        ::

            for page in api.iter_pages("Ticket", page_size=100):
                for ticket in page:
                    process(ticket)
        """
        params = _boolify_params(kwargs)
        start = 0
        fetched = 0

        while True:
            end = start + page_size - 1
            params["range"] = f"{start}-{end}"

            page_items, resp_headers = self._request_with_headers(
                "GET", itemtype, params=params
            )

            if not page_items:
                return

            fetched += len(page_items)
            yield page_items

            total = _parse_content_range(
                resp_headers.get("Content-Range", "")
            )

            if total is not None and fetched >= total:
                return
            if len(page_items) < page_size:
                return

            start += page_size

    def search(self, itemtype: str, **kwargs: Any) -> dict:
        """Run the GLPI search engine.

        Parameters
        ----------
        itemtype : str
            ``"AllAssets"`` for a cross-type search.
        **kwargs
            ``criteria``, ``metacriteria``, ``sort``, ``order``,
            ``range``, ``forcedisplay``, ``rawdata``, ``withindexes``.
        """
        params = _boolify_params(_flatten_search_params(kwargs))
        return self._request("GET", f"search/{itemtype}", params=params)

    def create_item(self, itemtype: str, input_data: Any, **kwargs: Any) -> Any:
        """Create one or several items."""
        payload: dict = {"input": input_data}
        payload.update(kwargs)
        return self._request("POST", itemtype, json=payload)

    def update_item(self, itemtype: str, input_data: Any, **kwargs: Any) -> list:
        """Update one or several items. Each dict must contain an ``"id"`` key."""
        payload: dict = {"input": input_data}
        payload.update(kwargs)
        return self._request("PUT", itemtype, json=payload)

    def delete_item(
        self,
        itemtype: str,
        input_data: Any,
        force_purge: bool = False,
        history: bool = True,
    ) -> list:
        """Delete one or several items.

        Parameters
        ----------
        force_purge : bool
            Bypass the trash and permanently delete.
        history : bool
            Whether to log the deletion in history.
        """
        payload: dict = {
            "input": input_data,
            "force_purge": int(force_purge),
            "history": int(history),
        }
        return self._request("DELETE", itemtype, json=payload)

    # ------------------------------------------------------------------
    # Sub-items
    # ------------------------------------------------------------------

    def get_sub_items(
        self,
        itemtype: str,
        item_id: int,
        sub_itemtype: str,
        **kwargs: Any,
    ) -> list:
        """Return sub-items (e.g. followups, tasks, solutions of a ticket)."""
        params = _boolify_params(kwargs)
        return self._request(
            "GET", f"{itemtype}/{item_id}/{sub_itemtype}", params=params
        )

    def add_sub_item(
        self,
        itemtype: str,
        item_id: int,
        sub_itemtype: str,
        input_data: dict,
        **kwargs: Any,
    ) -> dict:
        """Add a sub-item to a parent resource."""
        payload: dict = {"input": input_data}
        payload.update(kwargs)
        return self._request(
            "POST", f"{itemtype}/{item_id}/{sub_itemtype}", json=payload
        )

    # ------------------------------------------------------------------
    # Misc
    # ------------------------------------------------------------------

    def list_item_types(self) -> list:
        """Return all available item-type names in this GLPI instance."""
        return self._request("GET", "listItemtypes")

    def upload_document(self, file_path: str, document_name: Optional[str] = None) -> dict:
        """Upload a file as a GLPI Document.

        Parameters
        ----------
        file_path : str
            Local path to the file to upload.
        document_name : str or None
            Display name. Defaults to the file basename.
        """
        import json as _json
        from pathlib import Path

        fp = Path(file_path)
        name = document_name or fp.name
        manifest = _json.dumps({"input": {"name": name, "filename": [fp.name]}})
        url = f"{self._base_url}/Document"
        headers = {k: v for k, v in self._default_headers().items() if k != "Content-Type"}

        with fp.open("rb") as fh:
            response = self._http.post(
                url, headers=headers,
                files={
                    "uploadManifest": (None, manifest, "application/json"),
                    "filename[0]": (fp.name, fh),
                },
                verify=self._verify_ssl,
                timeout=self._timeout,
            )
        _raise_for_glpi_error(response)
        return response.json()

version property

version: GLPIVersion

GLPI server version as a :class:~glpi_utils.version.GLPIVersion.

item

item(itemtype: str) -> ItemProxy

Return an :class:~glpi_utils._resource.ItemProxy for any itemtype.

Parameters:

Name Type Description Default
itemtype str

GLPI itemtype name (case-sensitive), e.g. "Ticket".

required
Source code in glpi_utils/api.py
def item(self, itemtype: str) -> ItemProxy:
    """Return an :class:`~glpi_utils._resource.ItemProxy` for any itemtype.

    Parameters
    ----------
    itemtype : str
        GLPI itemtype name (case-sensitive), e.g. ``"Ticket"``.
    """
    if itemtype not in self._proxies:
        self._proxies[itemtype] = ItemProxy(self, itemtype)
    return self._proxies[itemtype]

login

login(username: Optional[str] = None, password: Optional[str] = None, user_token: Optional[str] = None) -> None

Authenticate against GLPI and store the session token.

Parameters:

Name Type Description Default
username str or None
None
password str or None
None
user_token str or None

Personal API token from the user profile page (Remote access key).

None
Environment variables

GLPI_USER, GLPI_PASSWORD, GLPI_USER_TOKEN

Source code in glpi_utils/api.py
def login(
    self,
    username: Optional[str] = None,
    password: Optional[str] = None,
    user_token: Optional[str] = None,
) -> None:
    """Authenticate against GLPI and store the session token.

    Parameters
    ----------
    username : str or None
    password : str or None
    user_token : str or None
        Personal API token from the user profile page (*Remote access key*).

    Environment variables
    ---------------------
    ``GLPI_USER``, ``GLPI_PASSWORD``, ``GLPI_USER_TOKEN``
    """
    username   = username   or os.environ.get("GLPI_USER")
    password   = password   or os.environ.get("GLPI_PASSWORD")
    user_token = user_token or os.environ.get("GLPI_USER_TOKEN")

    auth_headers: dict = {}

    if user_token:
        auth_headers["Authorization"] = f"user_token {user_token}"
    elif username and password:
        credentials = b64encode(f"{username}:{password}".encode()).decode()
        auth_headers["Authorization"] = f"Basic {credentials}"
    else:
        raise GlpiAuthError(
            "Provide username+password or user_token (or set "
            "GLPI_USER/GLPI_PASSWORD/GLPI_USER_TOKEN)."
        )

    data = self._request("GET", "initSession", headers=auth_headers)
    self._session_token = data["session_token"]
    log.debug("Session established.")

logout

logout() -> None

Terminate the active GLPI session.

Source code in glpi_utils/api.py
def logout(self) -> None:
    """Terminate the active GLPI session."""
    if self._session_token:
        try:
            self._request("GET", "killSession")
        finally:
            self._session_token = None
            log.debug("Session terminated.")

get_my_profiles

get_my_profiles() -> list

Return profiles available to the current user.

Source code in glpi_utils/api.py
def get_my_profiles(self) -> list:
    """Return profiles available to the current user."""
    return self._request("GET", "getMyProfiles")["myprofiles"]

get_active_profile

get_active_profile() -> dict

Return the currently active profile.

Source code in glpi_utils/api.py
def get_active_profile(self) -> dict:
    """Return the currently active profile."""
    return self._request("GET", "getActiveProfile")["active_profile"]

set_active_profile

set_active_profile(profile_id: int) -> None

Switch to a different profile.

Source code in glpi_utils/api.py
def set_active_profile(self, profile_id: int) -> None:
    """Switch to a different profile."""
    self._request("POST", "changeActiveProfile", json={"profiles_id": profile_id})

get_my_entities

get_my_entities(is_recursive: bool = False) -> list

Return entities accessible to the current user.

Source code in glpi_utils/api.py
def get_my_entities(self, is_recursive: bool = False) -> list:
    """Return entities accessible to the current user."""
    return self._request(
        "GET", "getMyEntities", params={"is_recursive": int(is_recursive)}
    )["myentities"]

get_active_entities

get_active_entities() -> dict

Return the current active entity context.

Source code in glpi_utils/api.py
def get_active_entities(self) -> dict:
    """Return the current active entity context."""
    return self._request("GET", "getActiveEntities")["active_entity"]

set_active_entity

set_active_entity(entity_id: int, is_recursive: bool = False) -> None

Switch the active entity context.

Source code in glpi_utils/api.py
def set_active_entity(self, entity_id: int, is_recursive: bool = False) -> None:
    """Switch the active entity context."""
    self._request(
        "POST",
        "changeActiveEntities",
        json={"entities_id": entity_id, "is_recursive": int(is_recursive)},
    )

get_full_session

get_full_session() -> dict

Return the full PHP session data.

Source code in glpi_utils/api.py
def get_full_session(self) -> dict:
    """Return the full PHP session data."""
    return self._request("GET", "getFullSession")["session"]

get_glpi_config

get_glpi_config() -> dict

Return global GLPI configuration.

Source code in glpi_utils/api.py
def get_glpi_config(self) -> dict:
    """Return global GLPI configuration."""
    return self._request("GET", "getGlpiConfig")

get_item

get_item(itemtype: str, item_id: int, **kwargs: Any) -> dict

Return a single item by ID.

Parameters:

Name Type Description Default
itemtype str
required
item_id int
required
**kwargs Any

Extra query parameters: expand_dropdowns, with_logs, with_networkports, with_infocoms, etc.

{}
Source code in glpi_utils/api.py
def get_item(self, itemtype: str, item_id: int, **kwargs: Any) -> dict:
    """Return a single item by ID.

    Parameters
    ----------
    itemtype : str
    item_id : int
    **kwargs
        Extra query parameters: ``expand_dropdowns``, ``with_logs``,
        ``with_networkports``, ``with_infocoms``, etc.
    """
    params = _boolify_params(kwargs)
    return self._request("GET", f"{itemtype}/{item_id}", params=params)

get_all_items

get_all_items(itemtype: str, **kwargs: Any) -> list

Return a single page of items.

Use :meth:get_all_pages to retrieve every item across all pages automatically.

Parameters:

Name Type Description Default
itemtype str
required
**kwargs Any

range (default "0-49"), sort, order, searchText, is_deleted, expand_dropdowns, etc.

{}
Source code in glpi_utils/api.py
def get_all_items(self, itemtype: str, **kwargs: Any) -> list:
    """Return a **single page** of items.

    Use :meth:`get_all_pages` to retrieve every item across all pages
    automatically.

    Parameters
    ----------
    itemtype : str
    **kwargs
        ``range`` (default ``"0-49"``), ``sort``, ``order``,
        ``searchText``, ``is_deleted``, ``expand_dropdowns``, etc.
    """
    params = _boolify_params(kwargs)
    if "range" not in params:
        params["range"] = f"0-{DEFAULT_PAGE_SIZE - 1}"
    return self._request("GET", itemtype, params=params)

get_all_pages

get_all_pages(itemtype: str, page_size: int = DEFAULT_PAGE_SIZE, **kwargs: Any) -> list

Fetch all items of itemtype by iterating pages automatically.

GLPI paginates results via a range query parameter (0-49, 50-99, …) and reports the grand total in the Content-Range response header (0-49/1337). This method handles all of that transparently and returns a single flat list.

Parameters:

Name Type Description Default
itemtype str

GLPI itemtype, e.g. "Ticket".

required
page_size int

Items per request (default: 50). Increase for fewer round-trips on large datasets; decrease if responses are slow.

DEFAULT_PAGE_SIZE
**kwargs Any

Extra GLPI parameters passed to every page request: sort, order, searchText, is_deleted, expand_dropdowns, etc. Do not pass range here — it is managed internally.

{}

Returns:

Type Description
list

All matching items as a flat list of dicts.

Examples:

::

# All open tickets, sorted by date
tickets = api.get_all_pages(
    "Ticket",
    sort="date_mod",
    order="DESC",
    searchText={"status": "1"},
)

# Via the fluent proxy
computers = api.computer.get_all_pages(expand_dropdowns=True)
Source code in glpi_utils/api.py
def get_all_pages(
    self,
    itemtype: str,
    page_size: int = DEFAULT_PAGE_SIZE,
    **kwargs: Any,
) -> list:
    """Fetch **all** items of *itemtype* by iterating pages automatically.

    GLPI paginates results via a ``range`` query parameter (``0-49``,
    ``50-99``, …) and reports the grand total in the ``Content-Range``
    response header (``0-49/1337``).  This method handles all of that
    transparently and returns a single flat list.

    Parameters
    ----------
    itemtype : str
        GLPI itemtype, e.g. ``"Ticket"``.
    page_size : int
        Items per request (default: 50).  Increase for fewer round-trips
        on large datasets; decrease if responses are slow.
    **kwargs
        Extra GLPI parameters passed to every page request: ``sort``,
        ``order``, ``searchText``, ``is_deleted``,
        ``expand_dropdowns``, etc.  Do **not** pass ``range`` here —
        it is managed internally.

    Returns
    -------
    list
        All matching items as a flat list of dicts.

    Examples
    --------
    ::

        # All open tickets, sorted by date
        tickets = api.get_all_pages(
            "Ticket",
            sort="date_mod",
            order="DESC",
            searchText={"status": "1"},
        )

        # Via the fluent proxy
        computers = api.computer.get_all_pages(expand_dropdowns=True)
    """
    params = _boolify_params(kwargs)
    results: list = []
    start = 0

    while True:
        end = start + page_size - 1
        params["range"] = f"{start}-{end}"

        page_items, resp_headers = self._request_with_headers(
            "GET", itemtype, params=params
        )

        if not page_items:
            break

        results.extend(page_items)

        total = _parse_content_range(
            resp_headers.get("Content-Range", "")
        )

        log.debug(
            "Paginating %s: fetched %d/%s items (range %s-%s)",
            itemtype, len(results), total or "?", start, end,
        )

        # Stop if we know the grand total and have reached it
        if total is not None and len(results) >= total:
            break

        # Stop if GLPI returned fewer items than requested
        # (last partial page — no Content-Range header on some versions)
        if len(page_items) < page_size:
            break

        start += page_size

    return results

iter_pages

iter_pages(itemtype: str, page_size: int = DEFAULT_PAGE_SIZE, **kwargs: Any) -> Iterator[list]

Yield one page of items at a time.

Memory-efficient alternative to :meth:get_all_pages for very large datasets where you want to process each batch immediately rather than accumulating everything in RAM.

Parameters:

Name Type Description Default
itemtype str
required
page_size int
DEFAULT_PAGE_SIZE
**kwargs Any

Same as :meth:get_all_pages.

{}

Yields:

Type Description
list

One page (list of dicts) per iteration.

Examples:

::

for page in api.iter_pages("Ticket", page_size=100):
    for ticket in page:
        process(ticket)
Source code in glpi_utils/api.py
def iter_pages(
    self,
    itemtype: str,
    page_size: int = DEFAULT_PAGE_SIZE,
    **kwargs: Any,
) -> Iterator[list]:
    """Yield one page of items at a time.

    Memory-efficient alternative to :meth:`get_all_pages` for very large
    datasets where you want to process each batch immediately rather than
    accumulating everything in RAM.

    Parameters
    ----------
    itemtype : str
    page_size : int
    **kwargs
        Same as :meth:`get_all_pages`.

    Yields
    ------
    list
        One page (list of dicts) per iteration.

    Examples
    --------
    ::

        for page in api.iter_pages("Ticket", page_size=100):
            for ticket in page:
                process(ticket)
    """
    params = _boolify_params(kwargs)
    start = 0
    fetched = 0

    while True:
        end = start + page_size - 1
        params["range"] = f"{start}-{end}"

        page_items, resp_headers = self._request_with_headers(
            "GET", itemtype, params=params
        )

        if not page_items:
            return

        fetched += len(page_items)
        yield page_items

        total = _parse_content_range(
            resp_headers.get("Content-Range", "")
        )

        if total is not None and fetched >= total:
            return
        if len(page_items) < page_size:
            return

        start += page_size

search

search(itemtype: str, **kwargs: Any) -> dict

Run the GLPI search engine.

Parameters:

Name Type Description Default
itemtype str

"AllAssets" for a cross-type search.

required
**kwargs Any

criteria, metacriteria, sort, order, range, forcedisplay, rawdata, withindexes.

{}
Source code in glpi_utils/api.py
def search(self, itemtype: str, **kwargs: Any) -> dict:
    """Run the GLPI search engine.

    Parameters
    ----------
    itemtype : str
        ``"AllAssets"`` for a cross-type search.
    **kwargs
        ``criteria``, ``metacriteria``, ``sort``, ``order``,
        ``range``, ``forcedisplay``, ``rawdata``, ``withindexes``.
    """
    params = _boolify_params(_flatten_search_params(kwargs))
    return self._request("GET", f"search/{itemtype}", params=params)

create_item

create_item(itemtype: str, input_data: Any, **kwargs: Any) -> Any

Create one or several items.

Source code in glpi_utils/api.py
def create_item(self, itemtype: str, input_data: Any, **kwargs: Any) -> Any:
    """Create one or several items."""
    payload: dict = {"input": input_data}
    payload.update(kwargs)
    return self._request("POST", itemtype, json=payload)

update_item

update_item(itemtype: str, input_data: Any, **kwargs: Any) -> list

Update one or several items. Each dict must contain an "id" key.

Source code in glpi_utils/api.py
def update_item(self, itemtype: str, input_data: Any, **kwargs: Any) -> list:
    """Update one or several items. Each dict must contain an ``"id"`` key."""
    payload: dict = {"input": input_data}
    payload.update(kwargs)
    return self._request("PUT", itemtype, json=payload)

delete_item

delete_item(itemtype: str, input_data: Any, force_purge: bool = False, history: bool = True) -> list

Delete one or several items.

Parameters:

Name Type Description Default
force_purge bool

Bypass the trash and permanently delete.

False
history bool

Whether to log the deletion in history.

True
Source code in glpi_utils/api.py
def delete_item(
    self,
    itemtype: str,
    input_data: Any,
    force_purge: bool = False,
    history: bool = True,
) -> list:
    """Delete one or several items.

    Parameters
    ----------
    force_purge : bool
        Bypass the trash and permanently delete.
    history : bool
        Whether to log the deletion in history.
    """
    payload: dict = {
        "input": input_data,
        "force_purge": int(force_purge),
        "history": int(history),
    }
    return self._request("DELETE", itemtype, json=payload)

get_sub_items

get_sub_items(itemtype: str, item_id: int, sub_itemtype: str, **kwargs: Any) -> list

Return sub-items (e.g. followups, tasks, solutions of a ticket).

Source code in glpi_utils/api.py
def get_sub_items(
    self,
    itemtype: str,
    item_id: int,
    sub_itemtype: str,
    **kwargs: Any,
) -> list:
    """Return sub-items (e.g. followups, tasks, solutions of a ticket)."""
    params = _boolify_params(kwargs)
    return self._request(
        "GET", f"{itemtype}/{item_id}/{sub_itemtype}", params=params
    )

add_sub_item

add_sub_item(itemtype: str, item_id: int, sub_itemtype: str, input_data: dict, **kwargs: Any) -> dict

Add a sub-item to a parent resource.

Source code in glpi_utils/api.py
def add_sub_item(
    self,
    itemtype: str,
    item_id: int,
    sub_itemtype: str,
    input_data: dict,
    **kwargs: Any,
) -> dict:
    """Add a sub-item to a parent resource."""
    payload: dict = {"input": input_data}
    payload.update(kwargs)
    return self._request(
        "POST", f"{itemtype}/{item_id}/{sub_itemtype}", json=payload
    )

list_item_types

list_item_types() -> list

Return all available item-type names in this GLPI instance.

Source code in glpi_utils/api.py
def list_item_types(self) -> list:
    """Return all available item-type names in this GLPI instance."""
    return self._request("GET", "listItemtypes")

upload_document

upload_document(file_path: str, document_name: Optional[str] = None) -> dict

Upload a file as a GLPI Document.

Parameters:

Name Type Description Default
file_path str

Local path to the file to upload.

required
document_name str or None

Display name. Defaults to the file basename.

None
Source code in glpi_utils/api.py
def upload_document(self, file_path: str, document_name: Optional[str] = None) -> dict:
    """Upload a file as a GLPI Document.

    Parameters
    ----------
    file_path : str
        Local path to the file to upload.
    document_name : str or None
        Display name. Defaults to the file basename.
    """
    import json as _json
    from pathlib import Path

    fp = Path(file_path)
    name = document_name or fp.name
    manifest = _json.dumps({"input": {"name": name, "filename": [fp.name]}})
    url = f"{self._base_url}/Document"
    headers = {k: v for k, v in self._default_headers().items() if k != "Content-Type"}

    with fp.open("rb") as fh:
        response = self._http.post(
            url, headers=headers,
            files={
                "uploadManifest": (None, manifest, "application/json"),
                "filename[0]": (fp.name, fh),
            },
            verify=self._verify_ssl,
            timeout=self._timeout,
        )
    _raise_for_glpi_error(response)
    return response.json()