Asenion AI GraphQL API Guide

Overview

This guide provides comprehensive documentation for accessing the Asenion.ai GraphQL API for server-to-server integrations, including the Archer IRM connector.

Base URL: https://fairly.ai/graphql

Authentication: Bearer Token (no CSRF required for backend clients)


Table of Contents

  1. Authentication
  2. Common Queries
  3. Common Mutations
  4. Data Types Reference
  5. Error Handling
  6. Rate Limiting
  7. Best Practices

Authentication

Generate Bearer Token

Before making GraphQL requests, you need to generate a Bearer token.

Option 1: Using GraphQL (One-time setup)

First, authenticate with username/password to get session cookies:

curl -X POST "https://fairly.ai/auth/login" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "your-email@company.com",
    "password": "your-password"
  }' \
  -c cookies.txt \
  -b cookies.txt

Then generate a Bearer token:

curl -X POST "https://fairly.ai/graphql" \
  -H "Content-Type: application/json" \
  -H "X-CSRF-Token: YOUR_CSRF_TOKEN_FROM_COOKIES" \
  -b cookies.txt \
  -d '{
    "query": "mutation GenerateBearerToken($input: NewTokenGenerationInput!) { generateBearerAccessToken(input: $input) { token { token description expirationDate createdAt } } }",
    "variables": {
      "input": {
        "email": "your-email@company.com",
        "description": "Archer IRM Integration Token"
      }
    }
  }'

Response:

{
  "data": {
    "generateBearerAccessToken": {
      "token": {
        "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
        "description": "Archer IRM Integration Token",
        "expirationDate": "2026-03-09T00:00:00.000Z",
        "createdAt": "2025-12-09T10:30:00.000Z"
      }
    }
  }
}

Option 2: Using the UI

  1. Log in to Fairly.ai
  2. Navigate to Settings → API Access (or Settings → Connections)
  3. Click “Generate API Token”
  4. Enter a description (e.g., “Archer IRM Integration”)
  5. Copy the token (it will only be shown once)

Using the Bearer Token

Include the token in the Authorization header of all GraphQL requests:

curl -X POST "https://fairly.ai/graphql" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ projectList { _id name } }"
  }'

Common Queries

1. List All Projects (AI Systems)

Retrieve all AI systems/projects accessible to the authenticated user.

Query:

query GetAllProjects {
  projectList {
    _id
    name
    description
    projectType
    orgId
    organizationUnitId
    source
    riskStatus
    riskScore
    riskDimensions {
      identifier
      status
      score
    }
    completionPercent
    numControls
    numAnswers
    numTestScores
    createdAt
    updatedAt
    createdBy
    lifecycle {
      _id
      name
      currentStage {
        _id
        name
      }
    }
    assessments {
      _id
      name
      description
      status
      riskStatus
      riskScore
      completionPercent
      numControls
      numAnswers
      updatedAt
    }
    policyList {
      identifier
      version
      policyId
    }
  }
}

cURL Example:

curl -X POST "https://fairly.ai/graphql" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "query GetAllProjects { projectList { _id name projectType riskStatus riskScore completionPercent assessments { _id name status riskStatus completionPercent } } }"}'

Response:

{
  "data": {
    "projectList": [
      {
        "_id": "64abc123...",
        "name": "Customer Service Chatbot",
        "projectType": "AI_SYSTEM",
        "riskStatus": "MEDIUM",
        "riskScore": 65.5,
        "completionPercent": 78.2,
        "assessments": [
          {
            "_id": "assessment_123",
            "name": "EU AI Act Compliance",
            "status": "IN_PROGRESS",
            "riskStatus": "MEDIUM",
            "completionPercent": 78.2
          }
        ]
      }
    ]
  }
}

2. Get Specific Project Details

Retrieve detailed information about a specific project.

Query:

query GetProject($projectId: ID!) {
  projectFromId(_id: $projectId) {
    _id
    name
    description
    projectType
    source
    riskStatus
    riskScore
    inherentRiskStatus
    riskDimensions {
      identifier
      status
      score
    }
    alignmentStatus
    alignmentRiskData {
      status
      aligned
      notAligned
      notAvailable
    }
    technologyRiskData {
      status
      numLowRisk
      numMediumRisk
      numHighRisk
    }
    completionPercent
    numControls
    numAnswers
    numTestScores
    createdAt
    updatedAt
    createdBy
    lifecycle {
      _id
      name
      currentStage {
        _id
        name
        description
      }
    }
    assessments {
      _id
      name
      description
      status
      riskStatus
      riskScore
      maxRiskScore
      unacceptableRiskPercent
      acceptableRiskPercent
      completionPercent
      numControls
      numAnswers
      updatedAt
      enableVersioning
      latestVersionId
      policy {
        _id
        identifier
        version
        name
        description
        jurisdiction
      }
      answerBundles {
        _id
        controlBundleId
        numControls
        numAnswers
        completionPercent
        alignmentStatus
        maxScore
        rawScore
        updatedAt
      }
      assignments {
        assignedTo
        assignedBy
        assignedToName
        dueDate
        type
        status
        comments
        completedAt
      }
    }
    policyList {
      identifier
      version
      policyId
    }
    sharedWith {
      _id
      email
      role
      firstName
      lastName
    }
  }
}

cURL Example:

curl -X POST "https://fairly.ai/graphql" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query GetProject($projectId: ID!) { projectFromId(_id: $projectId) { _id name riskStatus riskScore riskDimensions { identifier status score } assessments { _id name status riskStatus completionPercent } } }",
    "variables": {
      "projectId": "64abc123..."
    }
  }'

3. Get Project Statistics (Paginated)

Retrieve paginated list of AI systems with filtering options.

Query:

query GetAISystems($pagination: PaginationInput, $organizationUnitId: ID) {
  aiSystemList(
    pagination: $pagination
    organizationUnitId: $organizationUnitId
  ) {
    projects {
      _id
      name
      description
      projectType
      riskStatus
      riskScore
      alignmentStatus
      completionPercent
      numControls
      numAnswers
      createdAt
      createdBy
      securityStatus
      privacyStatus
      biasStatus
    }
    totalCount
    hasNextPage
    hasPreviousPage
  }
}

Variables:

{
  "pagination": {
    "page": 1,
    "pageSize": 20,
    "alignmentFilter": "ALL",
    "riskFilter": "ALL",
    "sortDirection": "DESC"
  },
  "organizationUnitId": null
}

4. Get Dashboard Metrics

Retrieve high-level dashboard metrics for AI systems.

Query:

query GetDashboardMetrics($organizationUnitId: ID) {
  aiSystemDashboardMetrics(organizationUnitId: $organizationUnitId) {
    totalAISystems
    totalRiskScore
    totalControls
    totalTests
    totalAIUseCases
  }
  aiSystemRiskDistribution(organizationUnitId: $organizationUnitId) {
    low
    medium
    high
  }
  aiSystemAlignmentDistribution(organizationUnitId: $organizationUnitId) {
    aligned
    notAligned
  }
}

5. Get Assessment Details

Retrieve detailed information about a specific assessment.

Query:

query GetAssessmentWithVersion($assessmentId: ID!, $versionId: ID!) {
  assessmentWithVersion(assessmentId: $assessmentId, versionId: $versionId) {
    _id
    name
    description
    status
    riskStatus
    riskScore
    maxRiskScore
    completionPercent
    numControls
    numAnswers
    updatedAt
    policy {
      _id
      identifier
      version
      name
      description
      jurisdiction
    }
    answerBundles {
      _id
      controlBundleId
      numControls
      numAnswers
      completionPercent
      alignmentStatus
      answers {
        _id
        controlBundleId
        controlIdentifier
        alignmentStatus
        riskStatus
        riskScore
        values {
          answerOptionId
          value
          valueType
          extraValue
          labels
          createdAt
          updatedAt
        }
        createdAt
        updatedAt
      }
    }
  }
}

6. Get User Tasks (Paginated)

Retrieve tasks assigned to the authenticated user.

Query:

query GetUserTasks($input: UserTasksInput!) {
  userTasksPaginated(input: $input) {
    tasks {
      taskId
      projectId
      projectName
      projectType
      projectTypeLabel
      assessmentId
      assessmentName
      policyName
      assignmentType
      status
      dueDate
      assignedBy
      assignedTo
      assignedToName
      comments
      completedAt
    }
    totalCount
    hasNextPage
    hasPreviousPage
  }
}

Variables:

{
  "input": {
    "pagination": {
      "page": 1,
      "pageSize": 20,
      "sortDirection": "DESC"
    },
    "filter": "ASSIGNED",
    "status": "PENDING"
  }
}

Common Mutations

1. Create AI System

Create a new AI system/project.

Mutation:

mutation CreateAISystem($input: CreateAISystemInput!) {
  createAISystem(input: $input) {
    _id
    name
    description
    projectType
    source
    orgId
    organizationUnitId
    createdAt
    createdBy
    policyList {
      identifier
      version
      policyId
    }
  }
}

Variables:

{
  "input": {
    "name": "Customer Support AI",
    "description": "AI-powered customer support system",
    "projectType": "AI_SYSTEM",
    "source": "Fairly",
    "organizationUnitId": "orgunit_123",
    "policies": [
      {
        "policy": {
          "identifier": "eu_ai_act",
          "version": "1.0"
        },
        "bundles": [
          {
            "controlBundleId": "bundle_123"
          }
        ]
      }
    ],
    "members": [
      {
        "email": "user@company.com",
        "role": "ADMIN"
      }
    ]
  }
}

2. Add Assessment to Project

Add a new assessment to an existing project.

Mutation:

mutation AddAssessment($input: AddAssessmentInput!) {
  addProjectAssessment(input: $input) {
    project {
      _id
      name
      assessments {
        _id
        name
        description
        status
        riskStatus
        completionPercent
      }
    }
  }
}

Variables:

{
  "input": {
    "projectId": "64abc123...",
    "name": "EU AI Act Compliance Assessment",
    "description": "Assessment against EU AI Act requirements",
    "projectPolicy": {
      "policy": {
        "identifier": "eu_ai_act",
        "version": "1.0"
      }
    }
  }
}

3. Update Assessment Answer

Submit an answer to an assessment control.

Mutation:

mutation UpdateAnswer($input: UpdateAnswerInput!) {
  assessmentUpdateAnswer(input: $input) {
    assessment {
      _id
      completionPercent
      status
      updatedAt
    }
    updatedBundle {
      _id
      controlBundleId
      completionPercent
      numAnswers
    }
  }
}

Variables:

{
  "input": {
    "assessmentId": "assessment_123",
    "controlBundleId": "bundle_456",
    "controlIdentifier": "control_789",
    "values": [
      {
        "answerOptionId": "option_1",
        "value": "Yes",
        "labels": ["compliance"],
        "comment": ["Implemented per requirements"]
      }
    ]
  }
}

4. Update Assessment Status

Change the status of an assessment (e.g., submit for review, approve).

Mutation:

mutation UpdateAssessmentStatus($input: UpdateAssessmentStatusInput!) {
  assessmentUpdateStatus(input: $input) {
    assessment {
      _id
      name
      status
      updatedAt
    }
  }
}

Variables:

{
  "input": {
    "assessmentId": "assessment_123",
    "status": "SUBMITTED",
    "comments": "Ready for review",
    "sendNotification": true,
    "notificationData": {
      "to": "reviewer@company.com",
      "from": "user@company.com",
      "assignedMember": "Reviewer Name",
      "projectName": "Customer Support AI",
      "projectId": "64abc123...",
      "assessmentName": "EU AI Act Compliance",
      "purpose": "Please review this assessment"
    }
  }
}

5. Assign Assessment for Review

Assign an assessment to users for review or approval.

Mutation:

mutation AssignAssessment($input: AssessmentAssignReviewInput!) {
  assignAssessmentAction(input: $input) {
    assessment {
      _id
      name
      assignments {
        assignedTo
        assignedBy
        assignedToName
        dueDate
        type
        status
      }
    }
  }
}

Variables:

{
  "input": {
    "assessmentId": "assessment_123",
    "assignedTo": [
      {
        "email": "reviewer@company.com",
        "name": "Reviewer Name"
      }
    ],
    "dueDate": "2025-12-31T23:59:59.000Z",
    "type": "REVIEW",
    "sendNotification": true,
    "notificationData": {
      "to": ["reviewer@company.com"],
      "from": "user@company.com",
      "assigner": "User Name",
      "role": "Reviewer",
      "projectName": "Customer Support AI",
      "projectId": "64abc123...",
      "assessmentName": "EU AI Act Compliance",
      "purpose": "Please review this assessment"
    }
  }
}

6. Add MLflow Results

Add MLflow tracking results to a project assessment.

Mutation:

mutation AddMLflowInfo($input: AddMLflowInfoInput!) {
  addMLflowInfo(input: $input) {
    project {
      _id
      name
      assessments {
        _id
        mlflowResults {
          mlflowUrl
          experimentId
          runId
          modelName
        }
      }
    }
    error
  }
}

Variables:

{
  "input": {
    "projectId": "64abc123...",
    "assessmentId": "assessment_123",
    "mlflowInfo": {
      "mlflowUrl": "https://mlflow.yourcompany.com",
      "experimentId": "exp_456",
      "runId": "run_789",
      "modelName": "customer_service_v2",
      "additionalMetadata": {
        "framework": "tensorflow",
        "version": "2.1.0"
      }
    }
  }
}

Data Types Reference

Project Type

type Project {
  _id: ID!
  name: NEString!
  description: String
  projectType: ProjectType! # AI_SYSTEM, MODEL_CANDIDATE, DATASET, etc.
  source: Source # Fairly, Azure, GCP, AWS
  orgId: NEString!
  organizationUnitId: ID
  riskStatus: AssessmentRiskStatusType # LOW, MEDIUM, HIGH, UNACCEPTABLE
  riskScore: Float
  riskDimensions: [RiskDimension!]
  alignmentStatus: AlignmentStatus
  completionPercent: Float
  numControls: Int
  numAnswers: Int
  assessments: [Assessment!]!
  policyList: [PolicyRef!]!
  lifecycle: LifeCycle
  createdAt: Timestamp
  updatedAt: Timestamp
  createdBy: NEString
}

Assessment Type

type Assessment {
  _id: ID!
  name: NEString!
  description: NEString
  policy: Policy!
  status: AssessmentStatusType! # NOT_STARTED, IN_PROGRESS, SUBMITTED, IN_REVIEW, APPROVED, REJECTED
  riskStatus: AssessmentRiskStatusType
  riskScore: Float
  maxRiskScore: Float
  unacceptableRiskPercent: Float
  acceptableRiskPercent: Float
  completionPercent: Float
  numControls: Float
  numAnswers: Float
  answerBundles: [AnswerBundle!]!
  assignments: [AssignmentRef]
  updatedAt: Timestamp
}

Risk Dimension Type

type RiskDimension {
  identifier: String! # e.g., "operational_risk", "model_risk", "compliance_risk"
  status: AssessmentRiskStatusType!
  score: Float!
}

Enums

enum ProjectType {
  AI_SYSTEM
  MODEL_CANDIDATE
  MODEL_CHAMPION
  FUNCTIONAL_MODEL
  DATASET
  VENDOR_AGENT
  AGENT_CANDIDATE
  VENDOR_MODEL
  ORGANIZATION
}

enum AssessmentStatusType {
  NOT_STARTED
  IN_PROGRESS
  SUBMITTED
  IN_REVIEW
  APPROVED
  REJECTED
}

enum AssessmentRiskStatusType {
  LOW
  MEDIUM
  HIGH
  UNACCEPTABLE
}

enum AlignmentStatus {
  ALIGNED
  NOT_ALIGNED
  N_A
}

enum Source {
  Fairly
  Azure
  GCP
  AWS
}

Error Handling

Authentication Errors

{
  "errors": [
    {
      "message": "Invalid token",
      "extensions": {
        "code": "UNAUTHENTICATED"
      }
    }
  ]
}

Solutions:

  • Verify Bearer token is correct
  • Check if token has expired
  • Generate a new token if needed

Authorization Errors

{
  "errors": [
    {
      "message": "Access denied. You do not have permission to access this project.",
      "extensions": {
        "code": "FORBIDDEN"
      }
    }
  ]
}

Solutions:

  • Ensure the user account has appropriate permissions
  • Check if the project is shared with the user
  • Verify organization membership

Validation Errors

{
  "errors": [
    {
      "message": "Variable \"$projectId\" of required type \"ID!\" was not provided.",
      "extensions": {
        "code": "BAD_USER_INPUT"
      }
    }
  ]
}

Solutions:

  • Check that all required variables are provided
  • Validate variable types match the schema
  • Review query syntax

Rate Limiting

Current Limits:

  • 1000 requests per hour per token
  • 100 requests per minute per token
  • Burst limit: 20 requests per 10 seconds

Rate Limit Headers:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 995
X-RateLimit-Reset: 1640995200

Rate Limit Exceeded Response:

{
  "errors": [
    {
      "message": "Rate limit exceeded. Please try again in 60 seconds.",
      "extensions": {
        "code": "RATE_LIMIT_EXCEEDED",
        "retryAfter": 60
      }
    }
  ]
}

Best Practices

1. Use Field Selection

Only request fields you need to reduce response size and improve performance:

# ❌ Bad: Requesting all fields
query GetProjects {
  projectList {
    _id
    name
    description
    # ... 30+ more fields
  }
}

# ✅ Good: Only necessary fields
query GetProjects {
  projectList {
    _id
    name
    riskStatus
    riskScore
  }
}

2. Use Pagination

For large datasets, always use pagination:

query GetAISystems($pagination: PaginationInput!) {
  aiSystemList(pagination: $pagination) {
    projects {
      _id
      name
      riskScore
    }
    totalCount
    hasNextPage
  }
}

3. Handle Errors Gracefully

const response = await fetch("https://fairly.ai/graphql", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ query, variables }),
});

const result = await response.json();

if (result.errors) {
  console.error("GraphQL Errors:", result.errors);
  // Handle specific error codes
  for (const error of result.errors) {
    if (error.extensions?.code === "UNAUTHENTICATED") {
      // Refresh token or re-authenticate
    }
  }
} else {
  // Process data
  console.log("Data:", result.data);
}

4. Use Variables for Dynamic Queries

# ✅ Good: Use variables
query GetProject($projectId: ID!) {
  projectFromId(_id: $projectId) {
    _id
    name
  }
}

# ❌ Bad: Hard-coded values
query GetProject {
  projectFromId(_id: "64abc123...") {
    _id
    name
  }
}
# ✅ Good: Single request for related data
query GetProjectAndMetrics($projectId: ID!) {
  projectFromId(_id: $projectId) {
    _id
    name
    riskScore
  }
  aiSystemDashboardMetrics {
    totalAISystems
    totalRiskScore
  }
}

# ❌ Bad: Multiple separate requests
# query1: projectFromId
# query2: aiSystemDashboardMetrics

6. Implement Token Refresh

class FairlyAPIClient {
  constructor(token, tokenExpiration) {
    this.token = token;
    this.tokenExpiration = new Date(tokenExpiration);
  }

  async query(query, variables) {
    // Check if token is expiring soon (within 7 days)
    const sevenDaysFromNow = new Date();
    sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7);

    if (this.tokenExpiration < sevenDaysFromNow) {
      await this.refreshToken();
    }

    return this.executeQuery(query, variables);
  }

  async refreshToken() {
    // Generate new token via GraphQL
    // Update this.token and this.tokenExpiration
  }
}

7. Use Persistent HTTP Connections

// Node.js example
const https = require("https");

const agent = new https.Agent({
  keepAlive: true,
  maxSockets: 10,
});

fetch("https://fairly.ai/graphql", {
  agent,
  method: "POST",
  // ... rest of config
});