ctfy.sdk.admin

Operator / admin client for the ctfy platform.

AdminClient is the operator-facing counterpart to ~ctfy.sdk.client.PlatformClient, mirroring the ctfy / ctfy-admin CLI split: the player client never surfaces admin methods, so agents and players reading the SDK reference aren't distracted by operator endpoints they can't call.

Reach it two ways:

  • client.admin on an existing PlatformClient — shares that client's transport (connection pool + bearer)::

    from ctfy.sdk import PlatformClient
    
    client = PlatformClient("https://ctfy.example", token="pf_xxx")
    client.admin.users.set_role(user_id, "admin")
    
  • AdminClient.connect() for operator tooling that doesn't already hold a player client::

    from ctfy.sdk import AdminClient
    
    with AdminClient.connect("https://ctfy.example", token="pf_xxx") as admin:
        admin.observability.overview()
    

Every endpoint here is role-gated server-side (admin / super-admin); the SDK only relays — the token's role decides access.

  1"""Operator / admin client for the ctfy platform.
  2
  3``AdminClient`` is the operator-facing counterpart to
  4:class:`~ctfy.sdk.client.PlatformClient`, mirroring the ``ctfy`` /
  5``ctfy-admin`` CLI split: the player client never surfaces admin methods, so
  6agents and players reading the SDK reference aren't distracted by operator
  7endpoints they can't call.
  8
  9Reach it two ways:
 10
 11* ``client.admin`` on an existing :class:`PlatformClient` — shares that
 12  client's transport (connection pool + bearer)::
 13
 14      from ctfy.sdk import PlatformClient
 15
 16      client = PlatformClient("https://ctfy.example", token="pf_xxx")
 17      client.admin.users.set_role(user_id, "admin")
 18
 19* :meth:`AdminClient.connect` for operator tooling that doesn't already hold
 20  a player client::
 21
 22      from ctfy.sdk import AdminClient
 23
 24      with AdminClient.connect("https://ctfy.example", token="pf_xxx") as admin:
 25          admin.observability.overview()
 26
 27Every endpoint here is role-gated server-side (admin / super-admin); the SDK
 28only relays — the token's role decides access.
 29"""
 30
 31from __future__ import annotations
 32
 33from functools import cached_property
 34from types import TracebackType
 35from typing import Self
 36
 37from ctfy.core.constants import DEFAULT_CLIENT_TIMEOUT
 38from ctfy.sdk.admin_resources.achievements import AdminAchievementsResource
 39from ctfy.sdk.admin_resources.announcements import AdminAnnouncementsResource
 40from ctfy.sdk.admin_resources.awd import AdminAwdResource
 41from ctfy.sdk.admin_resources.challenges import AdminChallengesResource
 42from ctfy.sdk.admin_resources.competition_admins import AdminCompetitionAdminsResource
 43from ctfy.sdk.admin_resources.competition_invites import AdminCompetitionInvitesResource
 44from ctfy.sdk.admin_resources.competitions import AdminCompetitionsResource
 45from ctfy.sdk.admin_resources.email import AdminEmailResource
 46from ctfy.sdk.admin_resources.eval import (
 47    AdminEvalCampaignsResource,
 48    AdminEvalHarnessesResource,
 49    AdminEvalModelsResource,
 50    AdminEvalRunsResource,
 51    AdminEvalVendorsResource,
 52)
 53from ctfy.sdk.admin_resources.instances import AdminInstancesResource
 54from ctfy.sdk.admin_resources.llm_providers import AdminLlmProvidersResource
 55from ctfy.sdk.admin_resources.nodes import AdminNodesResource
 56from ctfy.sdk.admin_resources.observability import AdminObservabilityResource
 57from ctfy.sdk.admin_resources.patches import AdminPatchesResource
 58from ctfy.sdk.admin_resources.records import AdminRecordsResource
 59from ctfy.sdk.admin_resources.registrations import AdminRegistrationsResource
 60from ctfy.sdk.admin_resources.reports import AdminReportsResource
 61from ctfy.sdk.admin_resources.series import AdminSeriesResource
 62from ctfy.sdk.admin_resources.settings import AdminSettingsResource
 63from ctfy.sdk.admin_resources.tasks import (
 64    AdminScheduledJobsResource,
 65    AdminTasksResource,
 66)
 67from ctfy.sdk.admin_resources.teams import AdminTeamsResource
 68from ctfy.sdk.admin_resources.users import AdminUsersResource
 69from ctfy.sdk.base import BaseHttpClient
 70
 71__all__ = ["AdminClient"]
 72
 73
 74class AdminClient:
 75    """Admin / operator surface, grouped into resource namespaces.
 76
 77    Namespaces: :attr:`users`, :attr:`competitions`, :attr:`announcements`,
 78    :attr:`achievements`, :attr:`challenges`, :attr:`instances`,
 79    :attr:`records`, :attr:`nodes`, :attr:`observability`, :attr:`settings`,
 80    :attr:`competition_admins`.
 81    """
 82
 83    def __init__(self, http: BaseHttpClient) -> None:
 84        #: Shared transport (the parent PlatformClient when reached via
 85        #: ``client.admin``, or an owned client when built via ``connect``).
 86        self._http = http
 87
 88    @classmethod
 89    def connect(
 90        cls,
 91        server_url: str,
 92        token: str = "",
 93        *,
 94        max_retries: int = 3,
 95        timeout: int = DEFAULT_CLIENT_TIMEOUT,
 96    ) -> AdminClient:
 97        """Build an ``AdminClient`` that owns its own transport.
 98
 99        For operator tooling that doesn't already hold a
100        :class:`PlatformClient`. The returned client is a context manager
101        that closes its transport on exit.
102        """
103        base = server_url.rstrip("/")
104        http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries)
105        return cls(http)
106
107    @cached_property
108    def users(self) -> AdminUsersResource:
109        return AdminUsersResource(self._http)
110
111    @cached_property
112    def competitions(self) -> AdminCompetitionsResource:
113        return AdminCompetitionsResource(self._http)
114
115    @cached_property
116    def announcements(self) -> AdminAnnouncementsResource:
117        return AdminAnnouncementsResource(self._http)
118
119    @cached_property
120    def achievements(self) -> AdminAchievementsResource:
121        return AdminAchievementsResource(self._http)
122
123    @cached_property
124    def challenges(self) -> AdminChallengesResource:
125        return AdminChallengesResource(self._http)
126
127    @cached_property
128    def email(self) -> AdminEmailResource:
129        return AdminEmailResource(self._http)
130
131    @cached_property
132    def llm_providers(self) -> AdminLlmProvidersResource:
133        return AdminLlmProvidersResource(self._http)
134
135    @cached_property
136    def patches(self) -> AdminPatchesResource:
137        return AdminPatchesResource(self._http)
138
139    @cached_property
140    def reports(self) -> AdminReportsResource:
141        return AdminReportsResource(self._http)
142
143    @cached_property
144    def awd(self) -> AdminAwdResource:
145        return AdminAwdResource(self._http)
146
147    @cached_property
148    def series(self) -> AdminSeriesResource:
149        return AdminSeriesResource(self._http)
150
151    @cached_property
152    def instances(self) -> AdminInstancesResource:
153        return AdminInstancesResource(self._http)
154
155    @cached_property
156    def records(self) -> AdminRecordsResource:
157        return AdminRecordsResource(self._http)
158
159    @cached_property
160    def nodes(self) -> AdminNodesResource:
161        return AdminNodesResource(self._http)
162
163    @cached_property
164    def observability(self) -> AdminObservabilityResource:
165        return AdminObservabilityResource(self._http)
166
167    @cached_property
168    def settings(self) -> AdminSettingsResource:
169        return AdminSettingsResource(self._http)
170
171    @cached_property
172    def competition_admins(self) -> AdminCompetitionAdminsResource:
173        return AdminCompetitionAdminsResource(self._http)
174
175    @cached_property
176    def competition_invites(self) -> AdminCompetitionInvitesResource:
177        return AdminCompetitionInvitesResource(self._http)
178
179    @cached_property
180    def registrations(self) -> AdminRegistrationsResource:
181        """Entrant roster, eligibility review, and CSV export."""
182        return AdminRegistrationsResource(self._http)
183
184    @cached_property
185    def teams(self) -> AdminTeamsResource:
186        """Enforcement against a squad — disqualify and reinstate.
187
188        Separate from ``registrations`` on purpose: that one rules on
189        eligibility (which decides prizes), this one decides whether the
190        team is in the event at all.
191        """
192        return AdminTeamsResource(self._http)
193
194    @cached_property
195    def tasks(self) -> AdminTasksResource:
196        return AdminTasksResource(self._http)
197
198    @cached_property
199    def scheduled_jobs(self) -> AdminScheduledJobsResource:
200        return AdminScheduledJobsResource(self._http)
201
202    @cached_property
203    def eval_models(self) -> AdminEvalModelsResource:
204        return AdminEvalModelsResource(self._http)
205
206    @cached_property
207    def eval_vendors(self) -> AdminEvalVendorsResource:
208        return AdminEvalVendorsResource(self._http)
209
210    @cached_property
211    def eval_harnesses(self) -> AdminEvalHarnessesResource:
212        return AdminEvalHarnessesResource(self._http)
213
214    @cached_property
215    def eval_runs(self) -> AdminEvalRunsResource:
216        return AdminEvalRunsResource(self._http)
217
218    @cached_property
219    def eval_campaigns(self) -> AdminEvalCampaignsResource:
220        return AdminEvalCampaignsResource(self._http)
221
222    def close(self) -> None:
223        """Close the underlying transport.
224
225        Only call this on a standalone client built via :meth:`connect`;
226        when reached via ``PlatformClient.admin`` the transport is shared
227        with — and closed by — the parent player client.
228        """
229        self._http.close()
230
231    def __enter__(self) -> Self:
232        return self
233
234    def __exit__(
235        self,
236        exc_type: type[BaseException] | None,
237        exc: BaseException | None,
238        tb: TracebackType | None,
239    ) -> None:
240        self.close()
class AdminClient:
 75class AdminClient:
 76    """Admin / operator surface, grouped into resource namespaces.
 77
 78    Namespaces: :attr:`users`, :attr:`competitions`, :attr:`announcements`,
 79    :attr:`achievements`, :attr:`challenges`, :attr:`instances`,
 80    :attr:`records`, :attr:`nodes`, :attr:`observability`, :attr:`settings`,
 81    :attr:`competition_admins`.
 82    """
 83
 84    def __init__(self, http: BaseHttpClient) -> None:
 85        #: Shared transport (the parent PlatformClient when reached via
 86        #: ``client.admin``, or an owned client when built via ``connect``).
 87        self._http = http
 88
 89    @classmethod
 90    def connect(
 91        cls,
 92        server_url: str,
 93        token: str = "",
 94        *,
 95        max_retries: int = 3,
 96        timeout: int = DEFAULT_CLIENT_TIMEOUT,
 97    ) -> AdminClient:
 98        """Build an ``AdminClient`` that owns its own transport.
 99
100        For operator tooling that doesn't already hold a
101        :class:`PlatformClient`. The returned client is a context manager
102        that closes its transport on exit.
103        """
104        base = server_url.rstrip("/")
105        http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries)
106        return cls(http)
107
108    @cached_property
109    def users(self) -> AdminUsersResource:
110        return AdminUsersResource(self._http)
111
112    @cached_property
113    def competitions(self) -> AdminCompetitionsResource:
114        return AdminCompetitionsResource(self._http)
115
116    @cached_property
117    def announcements(self) -> AdminAnnouncementsResource:
118        return AdminAnnouncementsResource(self._http)
119
120    @cached_property
121    def achievements(self) -> AdminAchievementsResource:
122        return AdminAchievementsResource(self._http)
123
124    @cached_property
125    def challenges(self) -> AdminChallengesResource:
126        return AdminChallengesResource(self._http)
127
128    @cached_property
129    def email(self) -> AdminEmailResource:
130        return AdminEmailResource(self._http)
131
132    @cached_property
133    def llm_providers(self) -> AdminLlmProvidersResource:
134        return AdminLlmProvidersResource(self._http)
135
136    @cached_property
137    def patches(self) -> AdminPatchesResource:
138        return AdminPatchesResource(self._http)
139
140    @cached_property
141    def reports(self) -> AdminReportsResource:
142        return AdminReportsResource(self._http)
143
144    @cached_property
145    def awd(self) -> AdminAwdResource:
146        return AdminAwdResource(self._http)
147
148    @cached_property
149    def series(self) -> AdminSeriesResource:
150        return AdminSeriesResource(self._http)
151
152    @cached_property
153    def instances(self) -> AdminInstancesResource:
154        return AdminInstancesResource(self._http)
155
156    @cached_property
157    def records(self) -> AdminRecordsResource:
158        return AdminRecordsResource(self._http)
159
160    @cached_property
161    def nodes(self) -> AdminNodesResource:
162        return AdminNodesResource(self._http)
163
164    @cached_property
165    def observability(self) -> AdminObservabilityResource:
166        return AdminObservabilityResource(self._http)
167
168    @cached_property
169    def settings(self) -> AdminSettingsResource:
170        return AdminSettingsResource(self._http)
171
172    @cached_property
173    def competition_admins(self) -> AdminCompetitionAdminsResource:
174        return AdminCompetitionAdminsResource(self._http)
175
176    @cached_property
177    def competition_invites(self) -> AdminCompetitionInvitesResource:
178        return AdminCompetitionInvitesResource(self._http)
179
180    @cached_property
181    def registrations(self) -> AdminRegistrationsResource:
182        """Entrant roster, eligibility review, and CSV export."""
183        return AdminRegistrationsResource(self._http)
184
185    @cached_property
186    def teams(self) -> AdminTeamsResource:
187        """Enforcement against a squad — disqualify and reinstate.
188
189        Separate from ``registrations`` on purpose: that one rules on
190        eligibility (which decides prizes), this one decides whether the
191        team is in the event at all.
192        """
193        return AdminTeamsResource(self._http)
194
195    @cached_property
196    def tasks(self) -> AdminTasksResource:
197        return AdminTasksResource(self._http)
198
199    @cached_property
200    def scheduled_jobs(self) -> AdminScheduledJobsResource:
201        return AdminScheduledJobsResource(self._http)
202
203    @cached_property
204    def eval_models(self) -> AdminEvalModelsResource:
205        return AdminEvalModelsResource(self._http)
206
207    @cached_property
208    def eval_vendors(self) -> AdminEvalVendorsResource:
209        return AdminEvalVendorsResource(self._http)
210
211    @cached_property
212    def eval_harnesses(self) -> AdminEvalHarnessesResource:
213        return AdminEvalHarnessesResource(self._http)
214
215    @cached_property
216    def eval_runs(self) -> AdminEvalRunsResource:
217        return AdminEvalRunsResource(self._http)
218
219    @cached_property
220    def eval_campaigns(self) -> AdminEvalCampaignsResource:
221        return AdminEvalCampaignsResource(self._http)
222
223    def close(self) -> None:
224        """Close the underlying transport.
225
226        Only call this on a standalone client built via :meth:`connect`;
227        when reached via ``PlatformClient.admin`` the transport is shared
228        with — and closed by — the parent player client.
229        """
230        self._http.close()
231
232    def __enter__(self) -> Self:
233        return self
234
235    def __exit__(
236        self,
237        exc_type: type[BaseException] | None,
238        exc: BaseException | None,
239        tb: TracebackType | None,
240    ) -> None:
241        self.close()

Admin / operator surface, grouped into resource namespaces.

Namespaces: users, competitions, announcements, achievements, challenges, instances, records, nodes, observability, settings, competition_admins.

AdminClient(http: ctfy.sdk.base.BaseHttpClient)
84    def __init__(self, http: BaseHttpClient) -> None:
85        #: Shared transport (the parent PlatformClient when reached via
86        #: ``client.admin``, or an owned client when built via ``connect``).
87        self._http = http
@classmethod
def connect( cls, server_url: str, token: str = '', *, max_retries: int = 3, timeout: int = 600) -> AdminClient:
 89    @classmethod
 90    def connect(
 91        cls,
 92        server_url: str,
 93        token: str = "",
 94        *,
 95        max_retries: int = 3,
 96        timeout: int = DEFAULT_CLIENT_TIMEOUT,
 97    ) -> AdminClient:
 98        """Build an ``AdminClient`` that owns its own transport.
 99
100        For operator tooling that doesn't already hold a
101        :class:`PlatformClient`. The returned client is a context manager
102        that closes its transport on exit.
103        """
104        base = server_url.rstrip("/")
105        http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries)
106        return cls(http)

Build an AdminClient that owns its own transport.

For operator tooling that doesn't already hold a PlatformClient. The returned client is a context manager that closes its transport on exit.

108    @cached_property
109    def users(self) -> AdminUsersResource:
110        return AdminUsersResource(self._http)
112    @cached_property
113    def competitions(self) -> AdminCompetitionsResource:
114        return AdminCompetitionsResource(self._http)
116    @cached_property
117    def announcements(self) -> AdminAnnouncementsResource:
118        return AdminAnnouncementsResource(self._http)
120    @cached_property
121    def achievements(self) -> AdminAchievementsResource:
122        return AdminAchievementsResource(self._http)
124    @cached_property
125    def challenges(self) -> AdminChallengesResource:
126        return AdminChallengesResource(self._http)
128    @cached_property
129    def email(self) -> AdminEmailResource:
130        return AdminEmailResource(self._http)
132    @cached_property
133    def llm_providers(self) -> AdminLlmProvidersResource:
134        return AdminLlmProvidersResource(self._http)
136    @cached_property
137    def patches(self) -> AdminPatchesResource:
138        return AdminPatchesResource(self._http)
140    @cached_property
141    def reports(self) -> AdminReportsResource:
142        return AdminReportsResource(self._http)
144    @cached_property
145    def awd(self) -> AdminAwdResource:
146        return AdminAwdResource(self._http)
148    @cached_property
149    def series(self) -> AdminSeriesResource:
150        return AdminSeriesResource(self._http)
152    @cached_property
153    def instances(self) -> AdminInstancesResource:
154        return AdminInstancesResource(self._http)
156    @cached_property
157    def records(self) -> AdminRecordsResource:
158        return AdminRecordsResource(self._http)
160    @cached_property
161    def nodes(self) -> AdminNodesResource:
162        return AdminNodesResource(self._http)
164    @cached_property
165    def observability(self) -> AdminObservabilityResource:
166        return AdminObservabilityResource(self._http)
168    @cached_property
169    def settings(self) -> AdminSettingsResource:
170        return AdminSettingsResource(self._http)
172    @cached_property
173    def competition_admins(self) -> AdminCompetitionAdminsResource:
174        return AdminCompetitionAdminsResource(self._http)
176    @cached_property
177    def competition_invites(self) -> AdminCompetitionInvitesResource:
178        return AdminCompetitionInvitesResource(self._http)
180    @cached_property
181    def registrations(self) -> AdminRegistrationsResource:
182        """Entrant roster, eligibility review, and CSV export."""
183        return AdminRegistrationsResource(self._http)

Entrant roster, eligibility review, and CSV export.

185    @cached_property
186    def teams(self) -> AdminTeamsResource:
187        """Enforcement against a squad — disqualify and reinstate.
188
189        Separate from ``registrations`` on purpose: that one rules on
190        eligibility (which decides prizes), this one decides whether the
191        team is in the event at all.
192        """
193        return AdminTeamsResource(self._http)

Enforcement against a squad — disqualify and reinstate.

Separate from registrations on purpose: that one rules on eligibility (which decides prizes), this one decides whether the team is in the event at all.

195    @cached_property
196    def tasks(self) -> AdminTasksResource:
197        return AdminTasksResource(self._http)
199    @cached_property
200    def scheduled_jobs(self) -> AdminScheduledJobsResource:
201        return AdminScheduledJobsResource(self._http)
203    @cached_property
204    def eval_models(self) -> AdminEvalModelsResource:
205        return AdminEvalModelsResource(self._http)
207    @cached_property
208    def eval_vendors(self) -> AdminEvalVendorsResource:
209        return AdminEvalVendorsResource(self._http)
211    @cached_property
212    def eval_harnesses(self) -> AdminEvalHarnessesResource:
213        return AdminEvalHarnessesResource(self._http)
215    @cached_property
216    def eval_runs(self) -> AdminEvalRunsResource:
217        return AdminEvalRunsResource(self._http)
219    @cached_property
220    def eval_campaigns(self) -> AdminEvalCampaignsResource:
221        return AdminEvalCampaignsResource(self._http)
def close(self) -> None:
223    def close(self) -> None:
224        """Close the underlying transport.
225
226        Only call this on a standalone client built via :meth:`connect`;
227        when reached via ``PlatformClient.admin`` the transport is shared
228        with — and closed by — the parent player client.
229        """
230        self._http.close()

Close the underlying transport.

Only call this on a standalone client built via connect(); when reached via PlatformClient.admin the transport is shared with — and closed by — the parent player client.