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.admin_invites import AdminAdminInvitesResource
 40from ctfy.sdk.admin_resources.announcements import AdminAnnouncementsResource
 41from ctfy.sdk.admin_resources.authoring import AdminAuthoredChallengesResource
 42from ctfy.sdk.admin_resources.authoring_review import (
 43    AdminAuthoringReviewersResource,
 44    AdminCompetitionAuthoredChallengesResource,
 45)
 46from ctfy.sdk.admin_resources.awd import AdminAwdResource
 47from ctfy.sdk.admin_resources.challenge_schedule import AdminChallengeScheduleResource
 48from ctfy.sdk.admin_resources.challenge_stats import AdminChallengeStatsResource
 49from ctfy.sdk.admin_resources.challenges import AdminChallengesResource
 50from ctfy.sdk.admin_resources.changelog import AdminChangelogResource
 51from ctfy.sdk.admin_resources.competition_admins import AdminCompetitionAdminsResource
 52from ctfy.sdk.admin_resources.competition_applications import (
 53    AdminCompetitionApplicationsResource,
 54)
 55from ctfy.sdk.admin_resources.competition_attachments import (
 56    AdminCompetitionAttachmentsResource,
 57)
 58from ctfy.sdk.admin_resources.competition_boards import AdminCompetitionBoardsResource
 59from ctfy.sdk.admin_resources.competition_invites import AdminCompetitionInvitesResource
 60from ctfy.sdk.admin_resources.competition_topics import AdminCompetitionTopicsResource
 61from ctfy.sdk.admin_resources.competitions import AdminCompetitionsResource
 62from ctfy.sdk.admin_resources.contact_messages import AdminContactMessagesResource
 63from ctfy.sdk.admin_resources.email import AdminEmailResource
 64from ctfy.sdk.admin_resources.env_sharing import AdminEnvSharingResource
 65from ctfy.sdk.admin_resources.eval import (
 66    AdminEvalAllowancesResource,
 67    AdminEvalCampaignsResource,
 68    AdminEvalHarnessesResource,
 69    AdminEvalRunsResource,
 70    AdminEvalVendorsResource,
 71    AdminLlmModelsResource,
 72    AdminRegistryResource,
 73)
 74from ctfy.sdk.admin_resources.exclusion_groups import AdminExclusionGroupsResource
 75from ctfy.sdk.admin_resources.gauntlet import AdminGauntletResource
 76from ctfy.sdk.admin_resources.instances import AdminInstancesResource
 77from ctfy.sdk.admin_resources.institutions import AdminInstitutionsResource
 78from ctfy.sdk.admin_resources.ip_location import AdminIpLocationResource
 79from ctfy.sdk.admin_resources.llm_calls import AdminLlmCallsResource
 80from ctfy.sdk.admin_resources.llm_providers import AdminLlmProvidersResource
 81from ctfy.sdk.admin_resources.machines import AdminMachinesResource
 82from ctfy.sdk.admin_resources.messages import AdminMessagesResource
 83from ctfy.sdk.admin_resources.nodes import AdminNodesResource
 84from ctfy.sdk.admin_resources.observability import AdminObservabilityResource
 85from ctfy.sdk.admin_resources.patches import AdminPatchesResource
 86from ctfy.sdk.admin_resources.prewarm import AdminPrewarmResource
 87from ctfy.sdk.admin_resources.recap import AdminRecapResource
 88from ctfy.sdk.admin_resources.records import AdminRecordsResource
 89from ctfy.sdk.admin_resources.registrations import AdminRegistrationsResource
 90from ctfy.sdk.admin_resources.release_plan import AdminReleasePlanResource
 91from ctfy.sdk.admin_resources.reports import AdminReportsResource
 92from ctfy.sdk.admin_resources.risk import AdminRiskResource
 93from ctfy.sdk.admin_resources.series import AdminSeriesResource
 94from ctfy.sdk.admin_resources.settings import AdminSettingsResource
 95from ctfy.sdk.admin_resources.suites import AdminSuitesResource
 96from ctfy.sdk.admin_resources.tasks import (
 97    AdminScheduledJobsResource,
 98    AdminTasksResource,
 99)
100from ctfy.sdk.admin_resources.teams import AdminTeamsResource
101from ctfy.sdk.admin_resources.users import AdminUsersResource
102from ctfy.sdk.base import BaseHttpClient
103
104__all__ = ["AdminClient"]
105
106
107class AdminClient:
108    """Admin / operator surface, grouped into resource namespaces.
109
110    Namespaces: :attr:`users`, :attr:`competitions`, :attr:`suites`, :attr:`announcements`,
111    :attr:`achievements`, :attr:`challenges`, :attr:`gauntlet`,
112    :attr:`instances`,
113    :attr:`records`, :attr:`nodes`, :attr:`observability`, :attr:`settings`,
114    :attr:`competition_admins`.
115    """
116
117    def __init__(self, http: BaseHttpClient) -> None:
118        #: Shared transport (the parent PlatformClient when reached via
119        #: ``client.admin``, or an owned client when built via ``connect``).
120        self._http = http
121
122    @classmethod
123    def connect(
124        cls,
125        server_url: str,
126        token: str = "",
127        *,
128        max_retries: int = 2,
129        timeout: int = DEFAULT_CLIENT_TIMEOUT,
130    ) -> AdminClient:
131        """Build an ``AdminClient`` that owns its own transport.
132
133        For operator tooling that doesn't already hold a
134        :class:`PlatformClient`. The returned client is a context manager
135        that closes its transport on exit.
136        """
137        base = server_url.rstrip("/")
138        http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries)
139        return cls(http)
140
141    @cached_property
142    def users(self) -> AdminUsersResource:
143        return AdminUsersResource(self._http)
144
145    @cached_property
146    def competitions(self) -> AdminCompetitionsResource:
147        return AdminCompetitionsResource(self._http)
148
149    @cached_property
150    def suites(self) -> AdminSuitesResource:
151        """A suite through its own door: its own fields only (#2896)."""
152        return AdminSuitesResource(self._http)
153
154    @cached_property
155    def announcements(self) -> AdminAnnouncementsResource:
156        return AdminAnnouncementsResource(self._http)
157
158    @cached_property
159    def changelog(self) -> AdminChangelogResource:
160        return AdminChangelogResource(self._http)
161
162    @cached_property
163    def contact_messages(self) -> AdminContactMessagesResource:
164        """What visitors wrote on the Contact page (#3414)."""
165        return AdminContactMessagesResource(self._http)
166
167    @cached_property
168    def institutions(self) -> AdminInstitutionsResource:
169        return AdminInstitutionsResource(self._http)
170
171    @cached_property
172    def exclusion_groups(self) -> AdminExclusionGroupsResource:
173        return AdminExclusionGroupsResource(self._http)
174
175    @cached_property
176    def achievements(self) -> AdminAchievementsResource:
177        return AdminAchievementsResource(self._http)
178
179    @cached_property
180    def challenges(self) -> AdminChallengesResource:
181        return AdminChallengesResource(self._http)
182
183    @cached_property
184    def gauntlet(self) -> AdminGauntletResource:
185        return AdminGauntletResource(self._http)
186
187    @cached_property
188    def email(self) -> AdminEmailResource:
189        return AdminEmailResource(self._http)
190
191    @cached_property
192    def ip_location(self) -> AdminIpLocationResource:
193        return AdminIpLocationResource(self._http)
194
195    @cached_property
196    def llm_calls(self) -> AdminLlmCallsResource:
197        return AdminLlmCallsResource(self._http)
198
199    @cached_property
200    def llm_providers(self) -> AdminLlmProvidersResource:
201        return AdminLlmProvidersResource(self._http)
202
203    @cached_property
204    def patches(self) -> AdminPatchesResource:
205        return AdminPatchesResource(self._http)
206
207    @cached_property
208    def reports(self) -> AdminReportsResource:
209        return AdminReportsResource(self._http)
210
211    @cached_property
212    def awd(self) -> AdminAwdResource:
213        return AdminAwdResource(self._http)
214
215    @cached_property
216    def series(self) -> AdminSeriesResource:
217        return AdminSeriesResource(self._http)
218
219    @cached_property
220    def instances(self) -> AdminInstancesResource:
221        return AdminInstancesResource(self._http)
222
223    @cached_property
224    def records(self) -> AdminRecordsResource:
225        return AdminRecordsResource(self._http)
226
227    @cached_property
228    def nodes(self) -> AdminNodesResource:
229        return AdminNodesResource(self._http)
230
231    @cached_property
232    def observability(self) -> AdminObservabilityResource:
233        return AdminObservabilityResource(self._http)
234
235    @cached_property
236    def settings(self) -> AdminSettingsResource:
237        return AdminSettingsResource(self._http)
238
239    @cached_property
240    def competition_admins(self) -> AdminCompetitionAdminsResource:
241        return AdminCompetitionAdminsResource(self._http)
242
243    @cached_property
244    def admin_invites(self) -> AdminAdminInvitesResource:
245        """Invite an address with no account yet to be an administrator."""
246        return AdminAdminInvitesResource(self._http)
247
248    @cached_property
249    def competition_invites(self) -> AdminCompetitionInvitesResource:
250        return AdminCompetitionInvitesResource(self._http)
251
252    @cached_property
253    def competition_applications(self) -> AdminCompetitionApplicationsResource:
254        """Applications to a private competition, and the verdict on each."""
255        return AdminCompetitionApplicationsResource(self._http)
256
257    @cached_property
258    def competition_attachments(self) -> AdminCompetitionAttachmentsResource:
259        """A competition's rules / handbook / notice files (#1808)."""
260        return AdminCompetitionAttachmentsResource(self._http)
261
262    @cached_property
263    def competition_boards(self) -> AdminCompetitionBoardsResource:
264        """A competition's extra boards — published and shadow (#1965 G)."""
265        return AdminCompetitionBoardsResource(self._http)
266
267    @cached_property
268    def recap(self) -> AdminRecapResource:
269        """A competition's recap, its publication and its report email (#3023)."""
270        return AdminRecapResource(self._http)
271
272    @cached_property
273    def challenge_schedule(self) -> AdminChallengeScheduleResource:
274        """When each challenge of a competition appears and shuts (#2093)."""
275        return AdminChallengeScheduleResource(self._http)
276
277    @cached_property
278    def release_plan(self) -> AdminReleasePlanResource:
279        """Waves, per-challenge rules and hints: drafts, publish, release (#2093, #2101)."""
280        return AdminReleasePlanResource(self._http)
281
282    @cached_property
283    def env_sharing(self) -> AdminEnvSharingResource:
284        """How many teams stand on one environment, the groups behind it and
285        the health of the shared environments (#2129 B)."""
286        return AdminEnvSharingResource(self._http)
287
288    @cached_property
289    def machines(self) -> AdminMachinesResource:
290        """Machine logins handed to teams, captain-only (#2132)."""
291        return AdminMachinesResource(self._http)
292
293    @cached_property
294    def messages(self) -> AdminMessagesResource:
295        """Site messages from an event's organisers or the platform (#2073)."""
296        return AdminMessagesResource(self._http)
297
298    @cached_property
299    def prewarm(self) -> AdminPrewarmResource:
300        """Whether a competition's images are warm on its nodes (#2129)."""
301        return AdminPrewarmResource(self._http)
302
303    @cached_property
304    def competition_topics(self) -> AdminCompetitionTopicsResource:
305        """A competition's scheduled topics, released by the clock (#1965)."""
306        return AdminCompetitionTopicsResource(self._http)
307
308    @cached_property
309    def registrations(self) -> AdminRegistrationsResource:
310        """Entrant roster, eligibility review, and CSV export."""
311        return AdminRegistrationsResource(self._http)
312
313    @cached_property
314    def teams(self) -> AdminTeamsResource:
315        """Enforcement against a squad — disqualify and reinstate.
316
317        Separate from ``registrations`` on purpose: that one rules on
318        eligibility (which decides prizes), this one decides whether the
319        team is in the event at all.
320        """
321        return AdminTeamsResource(self._http)
322
323    @cached_property
324    def challenge_stats(self) -> AdminChallengeStatsResource:
325        """How players did against an event's challenges: the organiser's
326        table and the single-challenge reading (#2146)."""
327        return AdminChallengeStatsResource(self._http)
328
329    @cached_property
330    def risk(self) -> AdminRiskResource:
331        """Risk control: the case queue (organisers) and an account's sealed
332        sightings (platform administrators). Detection reports; people decide."""
333        return AdminRiskResource(self._http)
334
335    @cached_property
336    def tasks(self) -> AdminTasksResource:
337        return AdminTasksResource(self._http)
338
339    @cached_property
340    def scheduled_jobs(self) -> AdminScheduledJobsResource:
341        return AdminScheduledJobsResource(self._http)
342
343    @cached_property
344    def authored_challenges(self) -> AdminAuthoredChallengesResource:
345        """The review queue for player-authored challenges."""
346        return AdminAuthoredChallengesResource(self._http)
347
348    @cached_property
349    def authoring_reviewers(self) -> AdminAuthoringReviewersResource:
350        """Who may rule on one competition's uploads (super-admin)."""
351        return AdminAuthoringReviewersResource(self._http)
352
353    @cached_property
354    def competition_authored_challenges(self) -> AdminCompetitionAuthoredChallengesResource:
355        """One competition's uploads, for its delegated reviewer: read,
356        audit and rule — never publish."""
357        return AdminCompetitionAuthoredChallengesResource(self._http)
358
359    @cached_property
360    def llm_models(self) -> AdminLlmModelsResource:
361        return AdminLlmModelsResource(self._http)
362
363    @cached_property
364    def eval_vendors(self) -> AdminEvalVendorsResource:
365        return AdminEvalVendorsResource(self._http)
366
367    @cached_property
368    def eval_harnesses(self) -> AdminEvalHarnessesResource:
369        return AdminEvalHarnessesResource(self._http)
370
371    @property
372    def eval_allowances(self) -> AdminEvalAllowancesResource:
373        return AdminEvalAllowancesResource(self._http)
374
375    @property
376    def registry(self) -> AdminRegistryResource:
377        """Registry storage: the usage report and the manual reclaim."""
378        return AdminRegistryResource(self._http)
379
380    @cached_property
381    def eval_runs(self) -> AdminEvalRunsResource:
382        return AdminEvalRunsResource(self._http)
383
384    @cached_property
385    def eval_campaigns(self) -> AdminEvalCampaignsResource:
386        return AdminEvalCampaignsResource(self._http)
387
388    def close(self) -> None:
389        """Close the underlying transport.
390
391        Only call this on a standalone client built via :meth:`connect`;
392        when reached via ``PlatformClient.admin`` the transport is shared
393        with — and closed by — the parent player client.
394        """
395        self._http.close()
396
397    def __enter__(self) -> Self:
398        return self
399
400    def __exit__(
401        self,
402        exc_type: type[BaseException] | None,
403        exc: BaseException | None,
404        tb: TracebackType | None,
405    ) -> None:
406        self.close()
class AdminClient:
108class AdminClient:
109    """Admin / operator surface, grouped into resource namespaces.
110
111    Namespaces: :attr:`users`, :attr:`competitions`, :attr:`suites`, :attr:`announcements`,
112    :attr:`achievements`, :attr:`challenges`, :attr:`gauntlet`,
113    :attr:`instances`,
114    :attr:`records`, :attr:`nodes`, :attr:`observability`, :attr:`settings`,
115    :attr:`competition_admins`.
116    """
117
118    def __init__(self, http: BaseHttpClient) -> None:
119        #: Shared transport (the parent PlatformClient when reached via
120        #: ``client.admin``, or an owned client when built via ``connect``).
121        self._http = http
122
123    @classmethod
124    def connect(
125        cls,
126        server_url: str,
127        token: str = "",
128        *,
129        max_retries: int = 2,
130        timeout: int = DEFAULT_CLIENT_TIMEOUT,
131    ) -> AdminClient:
132        """Build an ``AdminClient`` that owns its own transport.
133
134        For operator tooling that doesn't already hold a
135        :class:`PlatformClient`. The returned client is a context manager
136        that closes its transport on exit.
137        """
138        base = server_url.rstrip("/")
139        http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries)
140        return cls(http)
141
142    @cached_property
143    def users(self) -> AdminUsersResource:
144        return AdminUsersResource(self._http)
145
146    @cached_property
147    def competitions(self) -> AdminCompetitionsResource:
148        return AdminCompetitionsResource(self._http)
149
150    @cached_property
151    def suites(self) -> AdminSuitesResource:
152        """A suite through its own door: its own fields only (#2896)."""
153        return AdminSuitesResource(self._http)
154
155    @cached_property
156    def announcements(self) -> AdminAnnouncementsResource:
157        return AdminAnnouncementsResource(self._http)
158
159    @cached_property
160    def changelog(self) -> AdminChangelogResource:
161        return AdminChangelogResource(self._http)
162
163    @cached_property
164    def contact_messages(self) -> AdminContactMessagesResource:
165        """What visitors wrote on the Contact page (#3414)."""
166        return AdminContactMessagesResource(self._http)
167
168    @cached_property
169    def institutions(self) -> AdminInstitutionsResource:
170        return AdminInstitutionsResource(self._http)
171
172    @cached_property
173    def exclusion_groups(self) -> AdminExclusionGroupsResource:
174        return AdminExclusionGroupsResource(self._http)
175
176    @cached_property
177    def achievements(self) -> AdminAchievementsResource:
178        return AdminAchievementsResource(self._http)
179
180    @cached_property
181    def challenges(self) -> AdminChallengesResource:
182        return AdminChallengesResource(self._http)
183
184    @cached_property
185    def gauntlet(self) -> AdminGauntletResource:
186        return AdminGauntletResource(self._http)
187
188    @cached_property
189    def email(self) -> AdminEmailResource:
190        return AdminEmailResource(self._http)
191
192    @cached_property
193    def ip_location(self) -> AdminIpLocationResource:
194        return AdminIpLocationResource(self._http)
195
196    @cached_property
197    def llm_calls(self) -> AdminLlmCallsResource:
198        return AdminLlmCallsResource(self._http)
199
200    @cached_property
201    def llm_providers(self) -> AdminLlmProvidersResource:
202        return AdminLlmProvidersResource(self._http)
203
204    @cached_property
205    def patches(self) -> AdminPatchesResource:
206        return AdminPatchesResource(self._http)
207
208    @cached_property
209    def reports(self) -> AdminReportsResource:
210        return AdminReportsResource(self._http)
211
212    @cached_property
213    def awd(self) -> AdminAwdResource:
214        return AdminAwdResource(self._http)
215
216    @cached_property
217    def series(self) -> AdminSeriesResource:
218        return AdminSeriesResource(self._http)
219
220    @cached_property
221    def instances(self) -> AdminInstancesResource:
222        return AdminInstancesResource(self._http)
223
224    @cached_property
225    def records(self) -> AdminRecordsResource:
226        return AdminRecordsResource(self._http)
227
228    @cached_property
229    def nodes(self) -> AdminNodesResource:
230        return AdminNodesResource(self._http)
231
232    @cached_property
233    def observability(self) -> AdminObservabilityResource:
234        return AdminObservabilityResource(self._http)
235
236    @cached_property
237    def settings(self) -> AdminSettingsResource:
238        return AdminSettingsResource(self._http)
239
240    @cached_property
241    def competition_admins(self) -> AdminCompetitionAdminsResource:
242        return AdminCompetitionAdminsResource(self._http)
243
244    @cached_property
245    def admin_invites(self) -> AdminAdminInvitesResource:
246        """Invite an address with no account yet to be an administrator."""
247        return AdminAdminInvitesResource(self._http)
248
249    @cached_property
250    def competition_invites(self) -> AdminCompetitionInvitesResource:
251        return AdminCompetitionInvitesResource(self._http)
252
253    @cached_property
254    def competition_applications(self) -> AdminCompetitionApplicationsResource:
255        """Applications to a private competition, and the verdict on each."""
256        return AdminCompetitionApplicationsResource(self._http)
257
258    @cached_property
259    def competition_attachments(self) -> AdminCompetitionAttachmentsResource:
260        """A competition's rules / handbook / notice files (#1808)."""
261        return AdminCompetitionAttachmentsResource(self._http)
262
263    @cached_property
264    def competition_boards(self) -> AdminCompetitionBoardsResource:
265        """A competition's extra boards — published and shadow (#1965 G)."""
266        return AdminCompetitionBoardsResource(self._http)
267
268    @cached_property
269    def recap(self) -> AdminRecapResource:
270        """A competition's recap, its publication and its report email (#3023)."""
271        return AdminRecapResource(self._http)
272
273    @cached_property
274    def challenge_schedule(self) -> AdminChallengeScheduleResource:
275        """When each challenge of a competition appears and shuts (#2093)."""
276        return AdminChallengeScheduleResource(self._http)
277
278    @cached_property
279    def release_plan(self) -> AdminReleasePlanResource:
280        """Waves, per-challenge rules and hints: drafts, publish, release (#2093, #2101)."""
281        return AdminReleasePlanResource(self._http)
282
283    @cached_property
284    def env_sharing(self) -> AdminEnvSharingResource:
285        """How many teams stand on one environment, the groups behind it and
286        the health of the shared environments (#2129 B)."""
287        return AdminEnvSharingResource(self._http)
288
289    @cached_property
290    def machines(self) -> AdminMachinesResource:
291        """Machine logins handed to teams, captain-only (#2132)."""
292        return AdminMachinesResource(self._http)
293
294    @cached_property
295    def messages(self) -> AdminMessagesResource:
296        """Site messages from an event's organisers or the platform (#2073)."""
297        return AdminMessagesResource(self._http)
298
299    @cached_property
300    def prewarm(self) -> AdminPrewarmResource:
301        """Whether a competition's images are warm on its nodes (#2129)."""
302        return AdminPrewarmResource(self._http)
303
304    @cached_property
305    def competition_topics(self) -> AdminCompetitionTopicsResource:
306        """A competition's scheduled topics, released by the clock (#1965)."""
307        return AdminCompetitionTopicsResource(self._http)
308
309    @cached_property
310    def registrations(self) -> AdminRegistrationsResource:
311        """Entrant roster, eligibility review, and CSV export."""
312        return AdminRegistrationsResource(self._http)
313
314    @cached_property
315    def teams(self) -> AdminTeamsResource:
316        """Enforcement against a squad — disqualify and reinstate.
317
318        Separate from ``registrations`` on purpose: that one rules on
319        eligibility (which decides prizes), this one decides whether the
320        team is in the event at all.
321        """
322        return AdminTeamsResource(self._http)
323
324    @cached_property
325    def challenge_stats(self) -> AdminChallengeStatsResource:
326        """How players did against an event's challenges: the organiser's
327        table and the single-challenge reading (#2146)."""
328        return AdminChallengeStatsResource(self._http)
329
330    @cached_property
331    def risk(self) -> AdminRiskResource:
332        """Risk control: the case queue (organisers) and an account's sealed
333        sightings (platform administrators). Detection reports; people decide."""
334        return AdminRiskResource(self._http)
335
336    @cached_property
337    def tasks(self) -> AdminTasksResource:
338        return AdminTasksResource(self._http)
339
340    @cached_property
341    def scheduled_jobs(self) -> AdminScheduledJobsResource:
342        return AdminScheduledJobsResource(self._http)
343
344    @cached_property
345    def authored_challenges(self) -> AdminAuthoredChallengesResource:
346        """The review queue for player-authored challenges."""
347        return AdminAuthoredChallengesResource(self._http)
348
349    @cached_property
350    def authoring_reviewers(self) -> AdminAuthoringReviewersResource:
351        """Who may rule on one competition's uploads (super-admin)."""
352        return AdminAuthoringReviewersResource(self._http)
353
354    @cached_property
355    def competition_authored_challenges(self) -> AdminCompetitionAuthoredChallengesResource:
356        """One competition's uploads, for its delegated reviewer: read,
357        audit and rule — never publish."""
358        return AdminCompetitionAuthoredChallengesResource(self._http)
359
360    @cached_property
361    def llm_models(self) -> AdminLlmModelsResource:
362        return AdminLlmModelsResource(self._http)
363
364    @cached_property
365    def eval_vendors(self) -> AdminEvalVendorsResource:
366        return AdminEvalVendorsResource(self._http)
367
368    @cached_property
369    def eval_harnesses(self) -> AdminEvalHarnessesResource:
370        return AdminEvalHarnessesResource(self._http)
371
372    @property
373    def eval_allowances(self) -> AdminEvalAllowancesResource:
374        return AdminEvalAllowancesResource(self._http)
375
376    @property
377    def registry(self) -> AdminRegistryResource:
378        """Registry storage: the usage report and the manual reclaim."""
379        return AdminRegistryResource(self._http)
380
381    @cached_property
382    def eval_runs(self) -> AdminEvalRunsResource:
383        return AdminEvalRunsResource(self._http)
384
385    @cached_property
386    def eval_campaigns(self) -> AdminEvalCampaignsResource:
387        return AdminEvalCampaignsResource(self._http)
388
389    def close(self) -> None:
390        """Close the underlying transport.
391
392        Only call this on a standalone client built via :meth:`connect`;
393        when reached via ``PlatformClient.admin`` the transport is shared
394        with — and closed by — the parent player client.
395        """
396        self._http.close()
397
398    def __enter__(self) -> Self:
399        return self
400
401    def __exit__(
402        self,
403        exc_type: type[BaseException] | None,
404        exc: BaseException | None,
405        tb: TracebackType | None,
406    ) -> None:
407        self.close()

Admin / operator surface, grouped into resource namespaces.

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

AdminClient(http: ctfy.sdk.base.BaseHttpClient)
118    def __init__(self, http: BaseHttpClient) -> None:
119        #: Shared transport (the parent PlatformClient when reached via
120        #: ``client.admin``, or an owned client when built via ``connect``).
121        self._http = http
@classmethod
def connect( cls, server_url: str, token: str = '', *, max_retries: int = 2, timeout: int = 600) -> AdminClient:
123    @classmethod
124    def connect(
125        cls,
126        server_url: str,
127        token: str = "",
128        *,
129        max_retries: int = 2,
130        timeout: int = DEFAULT_CLIENT_TIMEOUT,
131    ) -> AdminClient:
132        """Build an ``AdminClient`` that owns its own transport.
133
134        For operator tooling that doesn't already hold a
135        :class:`PlatformClient`. The returned client is a context manager
136        that closes its transport on exit.
137        """
138        base = server_url.rstrip("/")
139        http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries)
140        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.

142    @cached_property
143    def users(self) -> AdminUsersResource:
144        return AdminUsersResource(self._http)
146    @cached_property
147    def competitions(self) -> AdminCompetitionsResource:
148        return AdminCompetitionsResource(self._http)
150    @cached_property
151    def suites(self) -> AdminSuitesResource:
152        """A suite through its own door: its own fields only (#2896)."""
153        return AdminSuitesResource(self._http)

A suite through its own door: its own fields only (#2896).

155    @cached_property
156    def announcements(self) -> AdminAnnouncementsResource:
157        return AdminAnnouncementsResource(self._http)
159    @cached_property
160    def changelog(self) -> AdminChangelogResource:
161        return AdminChangelogResource(self._http)
163    @cached_property
164    def contact_messages(self) -> AdminContactMessagesResource:
165        """What visitors wrote on the Contact page (#3414)."""
166        return AdminContactMessagesResource(self._http)

What visitors wrote on the Contact page (#3414).

168    @cached_property
169    def institutions(self) -> AdminInstitutionsResource:
170        return AdminInstitutionsResource(self._http)
172    @cached_property
173    def exclusion_groups(self) -> AdminExclusionGroupsResource:
174        return AdminExclusionGroupsResource(self._http)
176    @cached_property
177    def achievements(self) -> AdminAchievementsResource:
178        return AdminAchievementsResource(self._http)
180    @cached_property
181    def challenges(self) -> AdminChallengesResource:
182        return AdminChallengesResource(self._http)
184    @cached_property
185    def gauntlet(self) -> AdminGauntletResource:
186        return AdminGauntletResource(self._http)
188    @cached_property
189    def email(self) -> AdminEmailResource:
190        return AdminEmailResource(self._http)
192    @cached_property
193    def ip_location(self) -> AdminIpLocationResource:
194        return AdminIpLocationResource(self._http)
196    @cached_property
197    def llm_calls(self) -> AdminLlmCallsResource:
198        return AdminLlmCallsResource(self._http)
200    @cached_property
201    def llm_providers(self) -> AdminLlmProvidersResource:
202        return AdminLlmProvidersResource(self._http)
204    @cached_property
205    def patches(self) -> AdminPatchesResource:
206        return AdminPatchesResource(self._http)
208    @cached_property
209    def reports(self) -> AdminReportsResource:
210        return AdminReportsResource(self._http)
212    @cached_property
213    def awd(self) -> AdminAwdResource:
214        return AdminAwdResource(self._http)
216    @cached_property
217    def series(self) -> AdminSeriesResource:
218        return AdminSeriesResource(self._http)
220    @cached_property
221    def instances(self) -> AdminInstancesResource:
222        return AdminInstancesResource(self._http)
224    @cached_property
225    def records(self) -> AdminRecordsResource:
226        return AdminRecordsResource(self._http)
228    @cached_property
229    def nodes(self) -> AdminNodesResource:
230        return AdminNodesResource(self._http)
232    @cached_property
233    def observability(self) -> AdminObservabilityResource:
234        return AdminObservabilityResource(self._http)
236    @cached_property
237    def settings(self) -> AdminSettingsResource:
238        return AdminSettingsResource(self._http)
240    @cached_property
241    def competition_admins(self) -> AdminCompetitionAdminsResource:
242        return AdminCompetitionAdminsResource(self._http)
244    @cached_property
245    def admin_invites(self) -> AdminAdminInvitesResource:
246        """Invite an address with no account yet to be an administrator."""
247        return AdminAdminInvitesResource(self._http)

Invite an address with no account yet to be an administrator.

249    @cached_property
250    def competition_invites(self) -> AdminCompetitionInvitesResource:
251        return AdminCompetitionInvitesResource(self._http)
253    @cached_property
254    def competition_applications(self) -> AdminCompetitionApplicationsResource:
255        """Applications to a private competition, and the verdict on each."""
256        return AdminCompetitionApplicationsResource(self._http)

Applications to a private competition, and the verdict on each.

258    @cached_property
259    def competition_attachments(self) -> AdminCompetitionAttachmentsResource:
260        """A competition's rules / handbook / notice files (#1808)."""
261        return AdminCompetitionAttachmentsResource(self._http)

A competition's rules / handbook / notice files (#1808).

263    @cached_property
264    def competition_boards(self) -> AdminCompetitionBoardsResource:
265        """A competition's extra boards — published and shadow (#1965 G)."""
266        return AdminCompetitionBoardsResource(self._http)

A competition's extra boards — published and shadow (#1965 G).

268    @cached_property
269    def recap(self) -> AdminRecapResource:
270        """A competition's recap, its publication and its report email (#3023)."""
271        return AdminRecapResource(self._http)

A competition's recap, its publication and its report email (#3023).

273    @cached_property
274    def challenge_schedule(self) -> AdminChallengeScheduleResource:
275        """When each challenge of a competition appears and shuts (#2093)."""
276        return AdminChallengeScheduleResource(self._http)

When each challenge of a competition appears and shuts (#2093).

278    @cached_property
279    def release_plan(self) -> AdminReleasePlanResource:
280        """Waves, per-challenge rules and hints: drafts, publish, release (#2093, #2101)."""
281        return AdminReleasePlanResource(self._http)

Waves, per-challenge rules and hints: drafts, publish, release (#2093, #2101).

283    @cached_property
284    def env_sharing(self) -> AdminEnvSharingResource:
285        """How many teams stand on one environment, the groups behind it and
286        the health of the shared environments (#2129 B)."""
287        return AdminEnvSharingResource(self._http)

How many teams stand on one environment, the groups behind it and the health of the shared environments (#2129 B).

289    @cached_property
290    def machines(self) -> AdminMachinesResource:
291        """Machine logins handed to teams, captain-only (#2132)."""
292        return AdminMachinesResource(self._http)

Machine logins handed to teams, captain-only (#2132).

294    @cached_property
295    def messages(self) -> AdminMessagesResource:
296        """Site messages from an event's organisers or the platform (#2073)."""
297        return AdminMessagesResource(self._http)

Site messages from an event's organisers or the platform (#2073).

299    @cached_property
300    def prewarm(self) -> AdminPrewarmResource:
301        """Whether a competition's images are warm on its nodes (#2129)."""
302        return AdminPrewarmResource(self._http)

Whether a competition's images are warm on its nodes (#2129).

304    @cached_property
305    def competition_topics(self) -> AdminCompetitionTopicsResource:
306        """A competition's scheduled topics, released by the clock (#1965)."""
307        return AdminCompetitionTopicsResource(self._http)

A competition's scheduled topics, released by the clock (#1965).

309    @cached_property
310    def registrations(self) -> AdminRegistrationsResource:
311        """Entrant roster, eligibility review, and CSV export."""
312        return AdminRegistrationsResource(self._http)

Entrant roster, eligibility review, and CSV export.

314    @cached_property
315    def teams(self) -> AdminTeamsResource:
316        """Enforcement against a squad — disqualify and reinstate.
317
318        Separate from ``registrations`` on purpose: that one rules on
319        eligibility (which decides prizes), this one decides whether the
320        team is in the event at all.
321        """
322        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.

324    @cached_property
325    def challenge_stats(self) -> AdminChallengeStatsResource:
326        """How players did against an event's challenges: the organiser's
327        table and the single-challenge reading (#2146)."""
328        return AdminChallengeStatsResource(self._http)

How players did against an event's challenges: the organiser's table and the single-challenge reading (#2146).

330    @cached_property
331    def risk(self) -> AdminRiskResource:
332        """Risk control: the case queue (organisers) and an account's sealed
333        sightings (platform administrators). Detection reports; people decide."""
334        return AdminRiskResource(self._http)

Risk control: the case queue (organisers) and an account's sealed sightings (platform administrators). Detection reports; people decide.

336    @cached_property
337    def tasks(self) -> AdminTasksResource:
338        return AdminTasksResource(self._http)
340    @cached_property
341    def scheduled_jobs(self) -> AdminScheduledJobsResource:
342        return AdminScheduledJobsResource(self._http)
344    @cached_property
345    def authored_challenges(self) -> AdminAuthoredChallengesResource:
346        """The review queue for player-authored challenges."""
347        return AdminAuthoredChallengesResource(self._http)

The review queue for player-authored challenges.

349    @cached_property
350    def authoring_reviewers(self) -> AdminAuthoringReviewersResource:
351        """Who may rule on one competition's uploads (super-admin)."""
352        return AdminAuthoringReviewersResource(self._http)

Who may rule on one competition's uploads (super-admin).

354    @cached_property
355    def competition_authored_challenges(self) -> AdminCompetitionAuthoredChallengesResource:
356        """One competition's uploads, for its delegated reviewer: read,
357        audit and rule — never publish."""
358        return AdminCompetitionAuthoredChallengesResource(self._http)

One competition's uploads, for its delegated reviewer: read, audit and rule — never publish.

360    @cached_property
361    def llm_models(self) -> AdminLlmModelsResource:
362        return AdminLlmModelsResource(self._http)
364    @cached_property
365    def eval_vendors(self) -> AdminEvalVendorsResource:
366        return AdminEvalVendorsResource(self._http)
368    @cached_property
369    def eval_harnesses(self) -> AdminEvalHarnessesResource:
370        return AdminEvalHarnessesResource(self._http)
372    @property
373    def eval_allowances(self) -> AdminEvalAllowancesResource:
374        return AdminEvalAllowancesResource(self._http)
376    @property
377    def registry(self) -> AdminRegistryResource:
378        """Registry storage: the usage report and the manual reclaim."""
379        return AdminRegistryResource(self._http)

Registry storage: the usage report and the manual reclaim.

381    @cached_property
382    def eval_runs(self) -> AdminEvalRunsResource:
383        return AdminEvalRunsResource(self._http)
385    @cached_property
386    def eval_campaigns(self) -> AdminEvalCampaignsResource:
387        return AdminEvalCampaignsResource(self._http)
def close(self) -> None:
389    def close(self) -> None:
390        """Close the underlying transport.
391
392        Only call this on a standalone client built via :meth:`connect`;
393        when reached via ``PlatformClient.admin`` the transport is shared
394        with — and closed by — the parent player client.
395        """
396        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.