Saltar a contenido

Tarea 9 min de lectura

Connect a security posture tool with the security auditor API key

The Security Auditor role gives an API key read-only access to how your organisation is set up: who can sign in, how they authenticate, which keys and links exist, and, when the plan includes action audits, what happened in the audit log. Give this key to your SSPM or security posture tool, so it can check your organisation against your policies without being able to change anything.

The key reads the organisation's security configuration through the organisationSecurity GraphQL query and the audit log through the auditActions query.

What the key can and cannot access

The key can The key cannot
Call the organisationSecurity and auditActions queries and read every field described on this page. Call any other query or mutation, including reading its own organisation's scans, vulnerabilities, tickets or assets.
Read metadata about API keys, shared links and integrations: names, prefixes, roles, dates and status. Read a secret. API keys, shared link tokens and integration credentials are never returned.
Read the audit events of the organisation, when the plan includes action audits. Change a setting, a member, a key or an integration.

Before you start

  • The Admin role in the organisation. Only admins can create API keys.
  • A signed-in session. An API key cannot create another key.
  • An API key only. The role cannot be given to a member or sent in an invitation.
  • Action audits on your plan, if your tool needs auditActions. See Audit events.

Create the key

Create the key from the GraphQL API, signed in as an admin. Open the sandbox at https://api.ostorlab.co/apis/graphql, see Sandbox, and run:

mutation {
  createApiKey(name: "Security posture tool", role: "security_auditor") {
    apiKey {
      secretKey
    }
  }
}

Copy secretKey and store it in the secret manager of your tool right away. Ostorlab stores only a hash of the key, so it cannot show it again. An optional expiryDate argument limits the life of the key. See Create and manage API keys for expiry and revocation.

Call the query

Send the key in the X-Api-Key header to https://api.ostorlab.co/apis/graphql_token/:

curl -s https://api.ostorlab.co/apis/graphql_token/ \
  -H "X-Api-Key: $OSTORLAB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "{ organisationSecurity { settings { isOtpRequired } } }"}'

The query returns the security posture of the organisation the key belongs to. Each section below shows one selection and the shape of its response. You can combine sections in a single query. The values shown are examples.

If your organisation uses an IP allowlist, the tool must call from a listed address.

Settings

The authentication and access settings in effect for the organisation.

{
  organisationSecurity {
    settings {
      isOtpRequired
      mfaPromptFrequency
      hasObjectLevelAccess
      isRestrictedDefaultLevelAccess
      hasActionAudits
      passwordPolicy {
        minLength
        requiresUpperAndLowerCase
        requiresNumber
        requiresSpecialCharacter
      }
      loginLockout {
        failureLimit
        cooloffMinutes
      }
      sessionPolicy {
        idleTimeoutMinutes
        maxTokenAgeMinutes
      }
      sso {
        enabled
        identityProviderEntityId
        signOnServiceUrl
        signOutServiceUrl
        domains
        autoProvision
        acceptPassword
        modifiedTime
      }
    }
  }
}
{
  "data": {
    "organisationSecurity": {
      "settings": {
        "isOtpRequired": true,
        "mfaPromptFrequency": "EVERY_LOGIN",
        "hasObjectLevelAccess": false,
        "isRestrictedDefaultLevelAccess": false,
        "hasActionAudits": true,
        "passwordPolicy": {
          "minLength": 12,
          "requiresUpperAndLowerCase": true,
          "requiresNumber": true,
          "requiresSpecialCharacter": true
        },
        "loginLockout": { "failureLimit": 5, "cooloffMinutes": 30 },
        "sessionPolicy": { "idleTimeoutMinutes": 60, "maxTokenAgeMinutes": 1440 },
        "sso": {
          "enabled": true,
          "identityProviderEntityId": "https://idp.example.com/entity",
          "signOnServiceUrl": "https://idp.example.com/sso",
          "signOutServiceUrl": null,
          "domains": ["example.com"],
          "autoProvision": true,
          "acceptPassword": false,
          "modifiedTime": "2026-10-01T09:30:00+00:00"
        }
      }
    }
  }
}
  • mfaPromptFrequency is EVERY_LOGIN: every password login asks a user with OTP enabled for a token. Logins through Google, GitHub or SAML single sign-on do not ask for one.
  • minLength is null when no minimum length is enforced. cooloffMinutes is null when a lockout never expires.
  • sso is null when the organisation has no SAML configuration. acceptPassword tells whether users of the SSO domains can still sign in with a password.
  • hasActionAudits tells whether audit events are recorded and readable.

Members

The users with access to the organisation, one entry per access.

{
  organisationSecurity {
    members(page: 1, numberElements: 50) {
      members {
        id
        email
        username
        role
        mfaEnabled
        mfaMethods
        lastActivityTime
        dateJoined
        accessCreatedTime
        groups {
          id
          name
        }
      }
      pageInfo {
        count
        numPages
        hasNext
        hasPrevious
      }
    }
  }
}
{
  "data": {
    "organisationSecurity": {
      "members": {
        "members": [
          {
            "id": "12",
            "email": "alice@example.com",
            "username": "alice",
            "role": "admin",
            "mfaEnabled": true,
            "mfaMethods": ["totp", "recovery_codes"],
            "lastActivityTime": "2026-10-29T16:02:11+00:00",
            "dateJoined": "2026-01-12T08:00:00+00:00",
            "accessCreatedTime": "2026-01-12T08:05:00+00:00",
            "groups": [{ "id": "3", "name": "Mobile team" }]
          }
        ],
        "pageInfo": { "count": 1, "numPages": 1, "hasNext": false, "hasPrevious": false }
      }
    }
  }
}
  • Leave out page to get every member in one response. pageInfo is then null.
  • mfaEnabled tells whether login requires a one-time token from the user. mfaMethods lists the methods the user enrolled, and can be filled while mfaEnabled is false.
  • lastActivityTime is the last login of the user. It is refreshed on every token request, so it also tracks API activity.

Groups

The groups of the organisation, with their members and active API keys.

{
  organisationSecurity {
    groups {
      id
      name
      type
      parentId
      members
      apiKeys {
        name
        prefix
      }
    }
  }
}
{
  "data": {
    "organisationSecurity": {
      "groups": [
        {
          "id": "3",
          "name": "Mobile team",
          "type": "internal",
          "parentId": null,
          "members": ["alice@example.com", "bob@example.com"],
          "apiKeys": [{ "name": "GitHub Actions", "prefix": "AbCdEfGh" }]
        }
      ]
    }
  }
}

type is the ownership type of the group's owner, one of:

  • internal: assets the organisation owns.
  • acquisition: assets that came with an acquired company.
  • third_party_service: assets run by an outside provider.
  • rejected: assets marked as not belonging to the organisation.

members lists the emails of the users in the group. apiKeys lists active keys only: not revoked and not expired.

Roles

The roles a member can be given and the permission actions each one grants.

{
  organisationSecurity {
    roles {
      name
      permissions
    }
  }
}
{
  "data": {
    "organisationSecurity": {
      "roles": [
        { "name": "reader", "permissions": ["read", "attack_surface_read", "self_read_write"] },
        { "name": "attack_surface_auditor", "permissions": ["attack_surface_audit", "attack_surface_read", "self_read_write"] }
      ]
    }
  }
}

The security_auditor role is not listed: it cannot be assigned to a member.

API keys

The API keys of the organisation, newest first. Revoked keys are left out unless you ask for them.

{
  organisationSecurity {
    apiKeys(includeRevoked: true) {
      prefix
      name
      role
      createdTime
      expiryDate
      lastUsed
      revoked
      owners {
        id
        name
      }
    }
  }
}
{
  "data": {
    "organisationSecurity": {
      "apiKeys": [
        {
          "prefix": "AbCdEfGh",
          "name": "GitHub Actions",
          "role": "user",
          "createdTime": "2026-09-01T10:00:00+00:00",
          "expiryDate": null,
          "lastUsed": "2026-10-29T22:15:00+00:00",
          "revoked": false,
          "owners": [{ "id": "3", "name": "Mobile team" }]
        }
      ]
    }
  }
}
  • The key itself and its hash are never returned. prefix identifies the key.
  • expiryDate is null when the key never expires. lastUsed is null when the key was never used, and is recorded at most once per refresh interval, so it can lag behind the last request.
  • owners lists the groups the key is restricted to. For an admin key it lists every group of the organisation.
  • Keys that Ostorlab creates for its own scans are not listed.

Invitations

The invitations to join the organisation, newest first.

{
  organisationSecurity {
    invitations(pending: true) {
      id
      invitee
      role
      inviterEmail
      createdTime
      accepted
      status
    }
  }
}
{
  "data": {
    "organisationSecurity": {
      "invitations": [
        {
          "id": "41",
          "invitee": "carol@example.com",
          "role": "user",
          "inviterEmail": "alice@example.com",
          "createdTime": "2026-10-28T14:20:00+00:00",
          "accepted": null,
          "status": null
        }
      ]
    }
  }
}

pending: true returns pending invitations only, pending: false returns answered ones only, and leaving it out returns all. accepted is null while the invitation is pending. status is free text and can be null.

The shared access links of the organisation, without their tokens.

{
  organisationSecurity {
    sharedAccessLinks {
      id
      prefix
      scanId
      createdTime
      revoked
      revokedTime
      lastAccessTime
      accessCount
    }
  }
}
{
  "data": {
    "organisationSecurity": {
      "sharedAccessLinks": [
        {
          "id": "7",
          "prefix": "QrStUvWx",
          "scanId": "4521",
          "createdTime": "2026-10-20T09:00:00+00:00",
          "revoked": false,
          "revokedTime": null,
          "lastAccessTime": "2026-10-27T13:45:00+00:00",
          "accessCount": 6
        }
      ]
    }
  }
}

scanId is null when the link shares the attack surface instead of a scan.

Integrations

A summary of every third-party integration the organisation configured. No credential is returned.

{
  organisationSecurity {
    integrations {
      integrationType
      id
      name
      provider
      enabled
      createdTime
      updatedTime
      endpointHost
      lastSyncStatus
    }
  }
}
{
  "data": {
    "organisationSecurity": {
      "integrations": [
        {
          "integrationType": "JIRA",
          "id": "2",
          "name": "Security board",
          "provider": null,
          "enabled": true,
          "createdTime": "2026-08-05T11:00:00+00:00",
          "updatedTime": "2026-09-14T08:30:00+00:00",
          "endpointHost": "example.atlassian.net",
          "lastSyncStatus": null
        }
      ]
    }
  }
}
  • integrationType is one of JIRA, LINEAR, APP_CENTER, APPCIRCLE, SLACK, VANTA, SOURCE_CODE_OAUTH, GIT_PERSONAL_ACCESS_TOKEN, STANDARD_GIT, SERVICENOW, WEBHOOK, GITHUB_APP_INSTALLATION, ORG_GITHUB_APPLICATION, ORG_GITHUB_APPLICATION_INSTALLATION, AWS_CLOUD_CONNECTOR, GCP_CLOUD_CONNECTOR, AZURE_CLOUD_CONNECTOR, AI_PROVIDER_API_KEY and REMOTE_MCP_SERVER.
  • id is unique per integration type.
  • endpointHost is only the host of the configured endpoint. It is null when the integration has no endpoint, or when its URL is itself a secret.
  • provider is filled for source code integrations.

Audit events

The audit events of the organisation are read with the top-level auditActions query, not through organisationSecurity. Use its cursor mode: pass afterId or first. Events come back oldest first, so you can poll for new ones.

query AuditActions($afterId: Int, $first: Int, $actions: [AuditActionType!]) {
  auditActions(afterId: $afterId, first: $first, actions: $actions) {
    audits {
      id
      createdTime
      event
      action
      ipAddress
    }
    hasMore
    nextAfterId
  }
}

With the variables { "afterId": 1840, "first": 100, "actions": ["LOGIN_FAILED", "ACCESS_CHANGE"] } the response looks like this.

{
  "data": {
    "auditActions": {
      "audits": [
        {
          "id": "1841",
          "createdTime": "2026-10-29T16:05:00+00:00",
          "event": "alice created an API key.",
          "action": "ACCESS_CHANGE",
          "ipAddress": "203.0.113.7"
        }
      ],
      "hasMore": false,
      "nextAfterId": 1841
    }
  }
}

Keep the nextAfterId of each response and send it as afterId in the next call: you then receive only the events you have not read. While hasMore is true, call again right away to page through a backlog. An event is returned only once it is 30 seconds old.

Audit events need the action audit feature on your plan. Without it, auditActions returns an empty list, hasMore is false and nextAfterId is the afterId you sent. The hasActionAudits setting tells you which case you are in.

For every argument, field, event kind and filter, and for the polling steps, see Read the audit log with the API.

Revoke or replace the key

Revoke the key when you stop using the tool or when it leaks. See Revoke or replace a key. A key you revoke stops working at once.

Next steps