Skip to content
Preview. The endpoints on this page are illustrative and are likely to change as they move toward general availability. Documentation is published in advance so you can start shaping your integration; treat request and response details as subject to revision.

Partner Delegation

A partner-admin organization — a Cirrus reseller, systems integrator, MSP, or enterprise HQ with subsidiary orgs — can hold a single Platform API token that acts on behalf of any of its child organizations. One token, N children, per-request selection via an HTTP header.

When to use delegation

Use delegation if you're building an integration that:

  • Manages many customer orgs from a single admin surface (systems integrators, MSPs)
  • Runs cross-subsidiary reporting where the same query fans out across children of a corporate HQ
  • Provisions Cirrus configuration for downstream customers as part of a broader onboarding flow (resellers)

Do not use delegation if:

  • You're building a single-tenant integration for one customer — the standard org-scoped token in Authentication is what you want.
  • You need to act on peer orgs that don't have a parent-child relationship with yours — delegation requires an existing partnership linkage that Cirrus has provisioned.

The X-Cirrus-Acting-On header

Any request from a partner-admin token can carry:

When the header is present and authorised, the org-scoping middleware treats the request as if it originated from the child. Every existing endpoint keeps working with no other changes — the same URL, same request body, same response shape.

Successful delegated responses echo the resolved child back in a response header so your client can verify its intent was honoured:

Requests without the header behave exactly as before — the token acts on its own issuing org. Fully backward-compatible.

X-Cirrus-Acting-On: org_<childId>
Cirrus-Acting-On: org_<childId>

Requirements

To use delegation, your token needs:

  1. The partner:act-as-child scope. Only issuable to tokens minted from a partner-admin organization. If your org isn't a partner-admin org, this scope option won't appear in the developer dashboard — contact us to discuss enabling the partnership relationship.
  2. The scope(s) for the action you're taking on the child. For example, to invite a user into a child org, your token needs both partner:act-as-child and users:write. Delegation gives you the authority to act on the child; the resource scopes gate what you're allowed to do.

The effective scope on a child is the intersection of:

  • Your token's scopes
  • The scopes the child has granted to your parent org

If your token holds users:write but the child has granted only users:read to your parentship, a write attempt against the child returns 403 delegation-scope-insufficient — the response body names the exact granted subset.

Endpoints on this page

MethodEndpointDescription
GET/api/v1alpha1/partner/managed-organizationsList the child orgs your token can act on, with per-child scope grants. Details ↓

Every other endpoint on the Platform API honours the X-Cirrus-Acting-On header — see Endpoint delegation contract below.


List managed organizations

GET /api/v1alpha1/partner/managed-organizations

Returns the child organizations your token can act on. Requires partner:act-as-child. Do not send X-Cirrus-Acting-On on this endpoint — it reads about your children, not on one of them.

Query parameters

ParameterTypeDescription
cursorstringStandard keyset cursor.
limitnumberClamped 1–100, default 25.
sortstringupdatedAt, createdAt, name, seats.total. Prefix - for descending. Default -updatedAt.
searchstring(optional) Case-insensitive substring on child org name.
industrystring(optional) Filter to a specific industry.
licenseTierstring(optional) Filter to a tier slug (starter, professional, enterprise).
hasActiveIssuesboolean(optional) Filter to children with an active support flag (e.g., expired trial, payment failure).

Response

  • grantedScopes[] — the intersection of your token's scopes and the child's grants to your parent org. This is exactly what your token can exercise against this child right now; it's the source of truth for UI capability gates.
  • hasActiveIssues — coarse partner-facing health flag. true when the child has an expired trial, a failed payment, an active security incident, or another condition Cirrus wants partners to surface in their admin UIs.
  • relationshipCreatedAt — when the parent-child linkage was established in Cirrus.

Errors

  • Standard pagination errors (invalid-cursor, invalid-sort-field)
  • 403 insufficient-scope — token doesn't hold partner:act-as-child

bash
curl -s "https://altus.cirrusinsight.com/api/v1alpha1/partner/managed-organizations?limit=25" \
  -H "X-Cirrus-Api-Key: $TOKEN"
python
import requests

response = requests.get(
    "https://altus.cirrusinsight.com/api/v1alpha1/partner/managed-organizations",
    headers={"X-Cirrus-Api-Key": token},
    params={"limit": 25},
)
response.raise_for_status()
page = response.json()
csharp
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web;

var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);

var query = HttpUtility.ParseQueryString(string.Empty);
query["limit"] = "25";

var uri = new UriBuilder(
    "https://altus.cirrusinsight.com/api/v1alpha1/partner/managed-organizations")
{
    Query = query.ToString()
}.Uri;

var response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
var page = await response.Content.ReadAsStringAsync();
json
{
  "items": [
    {
      "id": "org_...",
      "name": "Acme Retail",
      "primaryEmailDomain": "retail.acme.com",
      "industry": "Retail",
      "licenseTier": { "slug": "professional", "name": "Professional" },
      "seats": { "total": 25, "used": 18, "available": 7 },
      "grantedScopes": [
        "users:read",
        "users:write",
        "deals:read",
        "qna:read"
      ],
      "hasActiveIssues": false,
      "relationshipCreatedAt": "2026-05-01T00:00:00Z",
      "updatedAt": "2026-07-14T09:30:00Z"
    }
  ],
  "nextCursor": null
}

Endpoint delegation contract

Every endpoint on /api/v1alpha1/ honours X-Cirrus-Acting-On. The contract is uniform:

On success:

  • The endpoint operates as if the request originated from the child. All returned ids belong to the child; all writes affect the child's records.
  • The response includes a Cirrus-Acting-On: org_<childId> header echoing the resolved child.
  • Rate limits deduct from the child's per-org quota (with a per-partner sub-bucket so your fan-out doesn't starve the child's other callers).
  • Audit-log entries carry both your parent-org id (the authority) and the child-org id (the subject).

On failure:

StatusError typeWhen
400invalid-acting-on-orgHeader value isn't a valid org_... id (malformed, empty, or wrong prefix)
403delegation-scope-requiredToken doesn't hold partner:act-as-child
403cross-org-delegation-forbiddenTarget org isn't a child of your token's org (existence is never leaked — same response whether the child doesn't exist, isn't yours, or the relationship has been revoked)
403delegation-scope-insufficientYour token holds the required scope, but the child hasn't granted it to your parent. Problem detail includes the granted subset.

Idempotency keys are scoped per (token, child) — the same Idempotency-Key sent against two different children creates two independent operations, not a single deduped one.

Worked example

The end-to-end reseller flow — enumerate children, then act on one:

bash
# 1. Enumerate the child orgs this token can act on
curl -s "https://altus.cirrusinsight.com/api/v1alpha1/partner/managed-organizations" \
  -H "X-Cirrus-Api-Key: $TOKEN"

# 2. Read the target child's entitlements to preflight the invite
curl -s "https://altus.cirrusinsight.com/api/v1alpha1/organizations/me/entitlements" \
  -H "X-Cirrus-Api-Key: $TOKEN" \
  -H "X-Cirrus-Acting-On: org_01H8YKQ2N9RETAIL"

# 3. Invite a user into the child
curl -s -X POST "https://altus.cirrusinsight.com/api/v1alpha1/users" \
  -H "X-Cirrus-Api-Key: $TOKEN" \
  -H "X-Cirrus-Acting-On: org_01H8YKQ2N9RETAIL" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "email": "casey@retail.acme.com",
    "firstName": "Casey",
    "role": "User"
  }'
python
import requests
import uuid

parent_token = "ci_live_..."
target_child = "org_01H8YKQ2N9RETAIL"

def child_headers(child_id: str, extra: dict | None = None) -> dict:
    headers = {
        "X-Cirrus-Api-Key": parent_token,
        "X-Cirrus-Acting-On": child_id,
    }
    if extra:
        headers.update(extra)
    return headers

# 1. Enumerate children
children = requests.get(
    "https://altus.cirrusinsight.com/api/v1alpha1/partner/managed-organizations",
    headers={"X-Cirrus-Api-Key": parent_token},
).json()

# 2. Preflight the child's entitlements
entitlements = requests.get(
    "https://altus.cirrusinsight.com/api/v1alpha1/organizations/me/entitlements",
    headers=child_headers(target_child),
).json()

# 3. Invite
invite = requests.post(
    "https://altus.cirrusinsight.com/api/v1alpha1/users",
    headers=child_headers(target_child, {"Idempotency-Key": str(uuid.uuid4())}),
    json={
        "email": "casey@retail.acme.com",
        "firstName": "Casey",
        "role": "User",
    },
)
invite.raise_for_status()
csharp
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var parentToken = "ci_live_...";
var targetChild = "org_01H8YKQ2N9RETAIL";

var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", parentToken);

// 1. Enumerate children
var childrenResponse = await client.GetAsync(
    "https://altus.cirrusinsight.com/api/v1alpha1/partner/managed-organizations");
childrenResponse.EnsureSuccessStatusCode();

// 2. Preflight entitlements for the target child
var entitlementsRequest = new HttpRequestMessage(HttpMethod.Get,
    "https://altus.cirrusinsight.com/api/v1alpha1/organizations/me/entitlements");
entitlementsRequest.Headers.Add("X-Cirrus-Acting-On", targetChild);
var entitlementsResponse = await client.SendAsync(entitlementsRequest);
entitlementsResponse.EnsureSuccessStatusCode();

// 3. Invite a user into the child
var inviteBody = new
{
    email = "casey@retail.acme.com",
    firstName = "Casey",
    role = "User",
};
var inviteRequest = new HttpRequestMessage(HttpMethod.Post,
    "https://altus.cirrusinsight.com/api/v1alpha1/users")
{
    Content = new StringContent(
        JsonSerializer.Serialize(inviteBody),
        Encoding.UTF8,
        "application/json"),
};
inviteRequest.Headers.Add("X-Cirrus-Acting-On", targetChild);
inviteRequest.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var inviteResponse = await client.SendAsync(inviteRequest);
inviteResponse.EnsureSuccessStatusCode();

Rate limits

Rate limits protect the subject system, not the actor. When you send X-Cirrus-Acting-On: org_<childId>, that request deducts from the child's per-org quota — not your parent org's.

To prevent your fan-out from starving a child's other callers, each (parent, child) pair gets its own sub-bucket within the child's overall bucket. In practice: a burst of 100 delegated calls to child A consumes A's partner-delegated sub-bucket, leaving A's direct callers untouched. But once that sub-bucket is exhausted, your calls receive 429 with a Retry-After header even though A's overall bucket may have room.

Your parent org has its own quota for requests that don't carry the header (GET /partner/managed-organizations, GET /organizations/me, etc.).

Audit trail

Every write executed via delegation records dual attribution in the audit log:

  • parentOrgId — your token's issuing org (the authority)
  • orgId — the child (the subject)
  • tokenId — which of your tokens was used

Child-org admins see "delegated activity by Partner X" as a distinct filter in their own audit surface. This lets a customer whose data is managed by a partner see exactly what was done on their behalf, without having to trust the partner's own reporting.

Limitations

  • One-level hierarchy only. X-Cirrus-Acting-On targets a direct child of your token's issuing org. Grandparent chains don't exist in Cirrus's org model.
  • No cross-parent delegation. You can only act on children of your org; you can't act on another parent's children even if they've granted you scopes there. This is a security-boundary property, not a technical limit.
  • No user impersonation. The header switches org context, not user context. It doesn't pretend the request came from a specific user of the child. When per-user OAuth ships, delegation and impersonation will need to be reconciled.
  • No ad-hoc peer sharing. Delegation requires an existing partnership relationship that Cirrus has provisioned. Two unrelated orgs can't grant each other delegation on their own.

Raleigh, NC — a Cirruspath, Inc. company