Skip to content

Management Client

The ManagementClient provides access to the FoxNose Management API for administrative operations.

Initialization

from foxnose_sdk.management import ManagementClient
from foxnose_sdk.auth import JWTAuth

client = ManagementClient(
    base_url="https://api.foxnose.net",
    environment_key="your-environment-key",
    auth=JWTAuth.from_static_token("YOUR_ACCESS_TOKEN"),
    timeout=30.0,  # Optional: request timeout in seconds
)

Parameters

Parameter Type Required Description
base_url str No API base URL (default: https://api.foxnose.net)
environment_key str Yes Your environment identifier
auth AuthStrategy Yes Authentication strategy
timeout float No Request timeout in seconds (default: 30.0)
retry_config RetryConfig No Retry configuration
default_headers Mapping[str, str] No Headers to include in all requests

Using Model Objects as Identifiers

All client methods that accept a *_key string parameter also accept the corresponding model object. The SDK automatically extracts the .key attribute from the object. This means you can pass objects returned by the API directly into subsequent calls without manually extracting keys.

# Before: manually extracting keys
folder = client.get_folder("folder-key")
resources = client.list_resources(folder.key)
resource = client.get_resource(folder.key, resources.results[0].key)

# After: passing objects directly
folder = client.get_folder("folder-key")
resources = client.list_resources(folder)
resource = client.get_resource(folder, resources.results[0])

This works across all methods and supports chaining naturally:

# Create a resource and publish a revision in one flow
folder = client.get_folder("blog-posts")
resource = client.create_resource(folder, {"data": {"title": "New Post"}})
revision = client.create_revision(folder, resource, {"data": {"title": "Draft"}})
client.publish_revision(folder, resource, revision)

String keys continue to work everywhere — this is fully backward compatible.

Available Type Aliases

For type annotations in your own code, the SDK exports these reference types:

Type alias Accepts
FolderRef str or FolderSummary
ResourceRef str or ResourceSummary
RevisionRef str or RevisionSummary
ComponentRef str or ComponentSummary
SchemaVersionRef str or SchemaVersionSummary
OrgRef str or OrganizationSummary
ProjectRef str or ProjectSummary
EnvironmentRef str or EnvironmentSummary
ManagementRoleRef str or ManagementRoleSummary
FluxRoleRef str or FluxRoleSummary
ManagementAPIKeyRef str or ManagementAPIKeySummary
FluxAPIKeyRef str or FluxAPIKeySummary
APIRef str or APIInfo
from foxnose_sdk import FolderRef, ResourceRef

def publish_all(client, folder: FolderRef):
    resources = client.list_resources(folder)
    for resource in resources.results:
        revisions = client.list_revisions(folder, resource)
        # ...

API Folder Route Descriptions

When connecting a folder to a Flux API, you can configure per-route descriptions used by Flux /_router introspection.

connection = client.add_api_folder(
    api_key="api-key",
    folder_key="folder-key",
    allowed_methods=["get_many", "get_one"],
    description_get_one="Get one article by key",
    description_get_many="List published articles",
    description_search="Search published articles",
    description_schema="Read article schema",
)

print(connection.description_schema)

To update descriptions later:

updated = client.update_api_folder(
    "api-key",
    "folder-key",
    description_get_many="Public article feed",
    description_search="Search feed items",
)

Folder Operations

List Folders

folders = client.list_folders()
for folder in folders.results:
    print(f"{folder.name} (key: {folder.key})")

Get Folder

folder = client.get_folder("folder-key")
print(f"Name: {folder.name}")
print(f"Type: {folder.folder_type}")

Get Folder by Path

folder = client.get_folder_by_path("parent/child")

Create Folder

folder = client.create_folder({
    "name": "Blog Posts",
    "alias": "blog-posts",
    "folder_type": "collection",
    "content_type": "document",
})

Update Folder

folder = client.update_folder("folder-key", {
    "name": "Updated Name",
})

Delete Folder

client.delete_folder("folder-key")

Resource Operations

List Resources

resources = client.list_resources(
    "folder-key",
    params={"limit": 10, "offset": 0},
)

Get Resource

resource = client.get_resource("folder-key", "resource-key")

Create Resource

resource = client.create_resource(
    "folder-key",
    {
        "data": {
            "title": "My Article",
            "content": "Article content here...",
        },
    },
)

You can assign an external_id during creation to identify the resource by your own system's ID:

resource = client.create_resource(
    "folder-key",
    {"title": "Imported Article", "content": "..."},
    external_id="cms-article-42",
)

Upsert Resource

Create or update a resource in a single call using an external_id. If no resource with the given external_id exists in the folder, a new resource is created. If one already exists, a new revision is created for it.

# First call: creates the resource
resource = client.upsert_resource(
    "folder-key",
    {"title": "My Article", "content": "First version"},
    external_id="cms-article-42",
)

# Second call with the same external_id: updates (creates a new revision)
resource = client.upsert_resource(
    "folder-key",
    {"title": "My Article", "content": "Updated version"},
    external_id="cms-article-42",
)

For component-based folders, pass the component parameter:

resource = client.upsert_resource(
    "folder-key",
    {"title": "Product", "price": 29.99},
    external_id="product-100",
    component="product-component",
)
Parameter Type Required Description
folder_key FolderRef Yes Target folder key or object
payload dict Yes JSON payload matching the folder schema
external_id str Yes External identifier for the resource
component ComponentRef No Component key for composite folders

Batch Upsert Resources

Upsert many resources in parallel. The SDK fans out individual upsert_resource() calls using threads (sync client) or async tasks (async client), controlled by max_concurrency.

This is an SDK helper, not a separate Management API endpoint. Under the hood it executes concurrent PUT /v1/:env/folders/:folder/resources/?external_id=<value> calls.

from foxnose_sdk import BatchUpsertItem

items = [
    BatchUpsertItem(external_id="ext-1", payload={"title": "Article 1"}),
    BatchUpsertItem(external_id="ext-2", payload={"title": "Article 2"}),
    BatchUpsertItem(external_id="ext-3", payload={"title": "Article 3"}),
]

result = client.batch_upsert_resources("folder-key", items, max_concurrency=10)

print(f"Succeeded: {result.success_count}, Failed: {result.failure_count}")
for error in result.failed:
    print(f"  [{error.index}] {error.external_id}: {error.exception}")

Async usage:

result = await client.batch_upsert_resources("folder-key", items, max_concurrency=10)

Error handling modes:

  • fail_fast=False (default) — process all items, collect successes and failures in the result.
  • fail_fast=True — stop on the first error and raise it immediately.
# Raises on first failed upsert call
try:
    result = client.batch_upsert_resources("folder-key", items, fail_fast=True)
except Exception as exc:
    print(f"Batch stopped: {exc}")

Progress tracking:

result = client.batch_upsert_resources(
    "folder-key",
    items,
    on_progress=lambda done, total: print(f"{done}/{total}"),
)
Parameter Type Required Description
folder_key FolderRef Yes Target folder key or object
items Sequence[BatchUpsertItem] Yes Items to upsert
max_concurrency int No Max parallel workers (default 5)
fail_fast bool No Stop on first error (default False)
on_progress Callable[[int, int], None] No Progress callback (completed, total)

BatchUpsertItem fields:

Field Type Required Description
external_id str Yes External identifier for the resource
payload dict Yes JSON payload matching the folder schema
component str No Component key for composite folders

BatchUpsertResult attributes:

Attribute Type Description
succeeded list[ResourceSummary] Successfully upserted resources
failed list[BatchItemError] Failed items with error details
success_count int Number of successes
failure_count int Number of failures
total int Total processed items
has_failures bool Whether any items failed

Update Resource

resource = client.update_resource(
    "folder-key",
    "resource-key",
    {"name": "Updated Resource Name"},
)

Delete Resource

client.delete_resource("folder-key", "resource-key")

Get Published Data

data = client.get_resource_data("folder-key", "resource-key")

Revision Operations

List Revisions

revisions = client.list_revisions("folder-key", "resource-key")

Create Revision

revision = client.create_revision(
    "folder-key",
    "resource-key",
    {
        "data": {
            "title": "New Title",
            "content": "New content...",
        },
    },
)

Publish Revision

revision = client.publish_revision(
    "folder-key",
    "resource-key",
    "revision-key",
)

Validate Revision

result = client.validate_revision(
    "folder-key",
    "resource-key",
    "revision-key",
)
if result.get("errors"):
    print("Validation errors:", result["errors"])

Schema Operations

Folder Versions

# List versions
versions = client.list_folder_versions("folder-key")

# Create version
version = client.create_folder_version("folder-key", {"name": "v2.0"})

# Publish version
client.publish_folder_version("folder-key", "version-key")

Schema Fields

# List fields
fields = client.list_folder_fields("folder-key", "version-key")

# Create field
field = client.create_folder_field(
    "folder-key",
    "version-key",
    {
        "key": "title",
        "name": "Title",
        "type": "text",
        "required": True,
    },
)

# Update field
client.update_folder_field(
    "folder-key",
    "version-key",
    "title",
    {"description": "The article title"},
)

# Delete field
client.delete_folder_field("folder-key", "version-key", "title")

Role and Permission Operations

Management Roles

# List roles
roles = client.list_management_roles()

# Create role
role = client.create_management_role({
    "name": "Editor",
    "description": "Content editor role",
    "full_access": False,
})

# Add permission
client.upsert_management_role_permission(
    "role-key",
    {
        "content_type": "resources",
        "actions": ["read", "create", "update"],
        "all_objects": True,
    },
)

Flux Roles

# Create Flux role
role = client.create_flux_role({
    "name": "Reader",
    "description": "Read-only access",
})

# Add permission
client.upsert_flux_role_permission(
    "role-key",
    {
        "content_type": "flux-apis",
        "actions": ["read"],
        "all_objects": False,
    },
)

# Scope access to specific Flux APIs
client.add_flux_permission_object(
    "role-key",
    {
        "content_type": "flux-apis",
        "object_key": "api-key-1",
    },
)

objects = client.list_flux_permission_objects(
    "role-key", content_type="flux-apis"
)
# objects is a list[RolePermissionObject]

API Keys

# Management API key
mgmt_key = client.create_management_api_key({
    "description": "CI/CD Key",
    "role": "role-key-1",
})

# Flux API key
flux_key = client.create_flux_api_key({
    "description": "Frontend Key",
    "role": "reader-role",
})

Organization Operations

# List organizations
orgs = client.list_organizations()

# Get organization
org = client.get_organization("org-key")

# Get usage
usage = client.get_organization_usage("org-key")

Project Operations

# List projects
projects = client.list_projects("org-key")

# Create project
project = client.create_project("org-key", {
    "name": "My Project",
})

Environment Operations

# List environments
envs = client.list_environments("org-key", "project-key")

# Create environment
env = client.create_environment("org-key", "project-key", {
    "name": "staging",
    "region": "eu-west-1",
})

# Toggle environment
client.toggle_environment("org-key", "project-key", "env-key", is_enabled=True)

Async Client

The AsyncManagementClient provides the same methods with async/await support:

from foxnose_sdk.management import AsyncManagementClient

async def main():
    client = AsyncManagementClient(
        base_url="https://api.foxnose.net",
        environment_key="your-environment-key",
        auth=JWTAuth.from_static_token("YOUR_TOKEN"),
    )

    folders = await client.list_folders()

    await client.close()

Closing the Client

Always close the client to release resources:

client.close()

# Or for async:
await client.close()