Administration

If you have the appropriate permissions, you can use the client to manage project-level administrative settings.

Managing project secrets

project_id = "YOUR_PROJECT_ID"

project = client.get(f"admin/projects/{project_id}")
current_secrets = project.get("project", {}).get("secret", [])

new_secrets = current_secrets + [{"name": "NEW_API_KEY", "valueString": "secret-value"}]
client.post(f"admin/projects/{project_id}/secrets", new_secrets)

Managing project sites

project_id = "YOUR_PROJECT_ID"
project = client.get(f"admin/projects/{project_id}")
current_sites = project.get("project", {}).get("site", [])

new_sites = current_sites + [{"name": "New Site", "domain": ["new-app.example.com"]}]
client.post(f"admin/projects/{project_id}/sites", new_sites)

Private Medplum APIs

For administrative tasks without a dedicated helper method, you can call Medplum’s private endpoints via get(), post(), put(), and delete().

Example: invite a new user

membership = client.invite_user(
    project_id="YOUR_PROJECT_ID",
    resource_type="Practitioner",
    first_name="Alice",
    last_name="Smith",
    email="alice.smith@example.com",
    send_email=True,
)
print(membership["id"])

Example: repair or replace a user’s parameterized access

After invitation (or during a tenant move) the parameterized ProjectMembership.access slice that grants tenant access often needs to be set or rewritten. Three methods, all on the client and all atomic with If-Match-driven 412 retry:

  • merge_project_membership_access — replace the whole managed slice with a desired list (bulk reconcile).
  • add_project_membership_access_entry — atomically add one tenant.
  • remove_project_membership_access_entry — atomically remove one tenant.

Use the bulk form when you have the full desired list — typically a one-shot script during data migration, an admin tool that reads all of a user’s tenant assignments in one query, or a recovery job that restores a known-good state:

from pymedplum import make_project_membership_access

client.merge_project_membership_access(
    membership["id"],
    managed_access=[
        make_project_membership_access(
            "AccessPolicy/practice-policy",
            {"organization": "Organization/practice-a"},
        ),
        make_project_membership_access(
            "AccessPolicy/practice-policy",
            {"organization": "Organization/practice-b"},
        ),
    ],
    managed_policy_ids={"practice-policy"},
)

Use the single-entry forms when reacting to a UI event:

client.add_project_membership_access_entry(
    membership["id"],
    make_project_membership_access(
        "AccessPolicy/practice-policy",
        {"organization": "Organization/practice-c"},
    ),
    managed_policy_ids={"practice-policy"},
)

To revoke the entire managed grant in one shot without touching unrelated entries, call merge_project_membership_access with managed_access=[]. See ProjectMembership Access for the full contract.