Flow Logo

API Reference

GraphQL API

Flow's GraphQL API provides a flexible, efficient way to query and mutate data. This guide covers the complete schema, queries, mutations, and subscriptions available through the GraphQL endpoint.


GraphQL Endpoint

All GraphQL operations are available at:

https://api.flow.bio/graphql

Use POST requests with your query in the request body:

curl -X POST https://api.flow.bio/graphql \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"query": "{ me { id username email } }"}'

GraphQL Playground

Access the interactive GraphQL playground at:

https://api.flow.bio/graphql

The playground provides:

  • Schema exploration
  • Query autocompletion
  • Documentation browser
  • Query history
  • Variable editor

Authentication

Include your JWT token in the Authorization header:

Authorization: Bearer <access_token>

Python Example

import requests

query = """
  query GetMyInfo {
    me {
      id
      username
      email
      groups {
        id
        name
      }
    }
  }
"""

response = requests.post(
    'https://api.flow.bio/graphql',
    json={'query': query},
    headers={'Authorization': f'Bearer {token}'}
)

data = response.json()
print(data['data']['me'])

JavaScript Example

const query = `
  query GetMyInfo {
    me {
      id
      username
      email
    }
  }
`;

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

const { data } = await response.json();
console.log(data.me);

Core Types

User Type

type User {
  id: ID!
  username: String!
  email: String!
  firstName: String
  lastName: String
  fullName: String
  isAdmin: Boolean!
  canRunPipelines: Boolean!
  groups: [Group!]!
  sshKey: String
  dateJoined: DateTime!
  lastLogin: DateTime
  ownedSamples(
    limit: Int = 20
    offset: Int = 0
    filter: String
  ): SampleConnection!
  ownedProjects(
    limit: Int = 20
    offset: Int = 0
  ): ProjectConnection!
  ownedExecutions(
    limit: Int = 20
    offset: Int = 0
    status: ExecutionStatus
  ): ExecutionConnection!
}

Sample Type

type Sample {
  id: ID!
  name: String!
  organism: Organism!
  sampleType: SampleType!
  source: SampleSource
  purificationTarget: PurificationTarget
  condition: String
  replicateGroup: String
  project: Project
  owner: User!
  created: DateTime!
  modified: DateTime!
  metadata: JSON
  permissions: Permissions!
  data(
    limit: Int = 20
    offset: Int = 0
  ): DataConnection!
  filesets: [Fileset!]!
  executions: [Execution!]!
  sharedWith: [UserOrGroup!]!
}

Data Type

type Data {
  id: ID!
  filename: String!
  size: BigInt!
  dataType: DataType!
  md5: String
  created: DateTime!
  modified: DateTime!
  sample: Sample
  fileset: Fileset
  owner: User!
  permissions: Permissions!
  downloadUrl: String!
  metadata: JSON
  executions: [Execution!]!
}

Project Type

type Project {
  id: ID!
  name: String!
  description: String
  organism: Organism!
  isPublic: Boolean!
  owner: User!
  created: DateTime!
  modified: DateTime!
  permissions: Permissions!
  samples(
    limit: Int = 20
    offset: Int = 0
    filter: String
  ): SampleConnection!
  executions(
    limit: Int = 20
    offset: Int = 0
  ): ExecutionConnection!
  data: [Data!]!
  sharedWith: [UserOrGroup!]!
  sampleCount: Int!
  executionCount: Int!
}

Execution Type

type Execution {
  id: ID!
  name: String!
  status: ExecutionStatus!
  progress: Int
  pipeline: Pipeline!
  pipelineVersion: PipelineVersion!
  owner: User!
  created: DateTime!
  started: DateTime
  completed: DateTime
  duration: Int
  cluster: String
  permissions: Permissions!
  samples: [Sample!]!
  filesets: [Fileset!]!
  parameters: JSON
  outputs: [ExecutionOutput!]!
  logs: [ExecutionLog!]!
  processExecutions: [ProcessExecution!]!
  sharedWith: [UserOrGroup!]!
  downloadUrl: String
}

enum ExecutionStatus {
  PENDING
  RUNNING
  COMPLETED
  FAILED
  CANCELLED
}

Pipeline Type

type Pipeline {
  id: ID!
  name: String!
  description: String
  category: PipelineCategory!
  upstreamPipelines: [Pipeline!]!
  versions: [PipelineVersion!]!
  latestVersion: PipelineVersion
  isActive: Boolean!
}

type PipelineVersion {
  id: ID!
  pipeline: Pipeline!
  version: String!
  isLatest: Boolean!
  created: DateTime!
  parameters: [PipelineParameter!]!
  requiredDataTypes: [DataType!]!
  outputDataTypes: [DataType!]!
  estimatedDuration: Int
  canRun: Boolean!
}

Queries

User Queries

# Get current user
query GetMe {
  me {
    id
    username
    email
    groups {
      id
      name
      slug
    }
    canRunPipelines
  }
}

# Get user by ID
query GetUser($id: ID!) {
  user(id: $id) {
    id
    username
    email
    fullName
  }
}

# Search users
query SearchUsers($query: String!, $limit: Int = 20) {
  searchUsers(query: $query, limit: $limit) {
    id
    username
    email
    fullName
  }
}

Sample Queries

# Get sample by ID
query GetSample($id: ID!) {
  sample(id: $id) {
    id
    name
    organism {
      id
      name
      scientificName
    }
    sampleType {
      id
      name
    }
    project {
      id
      name
    }
    data {
      edges {
        node {
          id
          filename
          size
          dataType {
            name
          }
        }
      }
    }
    permissions {
      canEdit
      canShare
      canDelete
    }
  }
}

# List owned samples with pagination
query GetMySamples($limit: Int = 20, $offset: Int = 0, $filter: String) {
  me {
    ownedSamples(limit: $limit, offset: $offset, filter: $filter) {
      totalCount
      edges {
        node {
          id
          name
          organism {
            name
          }
          created
        }
      }
      pageInfo {
        hasNextPage
        hasPreviousPage
      }
    }
  }
}

# Search samples
query SearchSamples(
  $query: String!
  $organism: ID
  $sampleType: ID
  $project: ID
  $limit: Int = 50
) {
  searchSamples(
    query: $query
    organism: $organism
    sampleType: $sampleType
    project: $project
    limit: $limit
  ) {
    totalCount
    edges {
      node {
        id
        name
        organism {
          name
        }
        project {
          name
        }
      }
    }
  }
}

Project Queries

# Get project details
query GetProject($id: ID!) {
  project(id: $id) {
    id
    name
    description
    organism {
      id
      name
    }
    isPublic
    owner {
      username
    }
    sampleCount
    samples(limit: 100) {
      edges {
        node {
          id
          name
          sampleType {
            name
          }
        }
      }
    }
  }
}

# List public projects
query GetPublicProjects($organism: ID, $limit: Int = 20) {
  publicProjects(organism: $organism, limit: $limit) {
    edges {
      node {
        id
        name
        description
        organism {
          name
        }
        sampleCount
        owner {
          username
        }
      }
    }
  }
}

Execution Queries

# Get execution details
query GetExecution($id: ID!) {
  execution(id: $id) {
    id
    name
    status
    progress
    pipeline {
      name
    }
    pipelineVersion {
      version
    }
    created
    started
    completed
    duration
    samples {
      id
      name
    }
    parameters
    logs {
      timestamp
      process
      message
      level
    }
    outputs {
      id
      filename
      size
      downloadUrl
    }
  }
}

# Monitor execution progress
query MonitorExecution($id: ID!) {
  execution(id: $id) {
    status
    progress
    processExecutions {
      process
      status
      progress
      startTime
      endTime
      exitCode
    }
  }
}

# List running executions
query GetRunningExecutions {
  executions(status: RUNNING) {
    edges {
      node {
        id
        name
        status
        progress
        pipeline {
          name
        }
        owner {
          username
        }
      }
    }
  }
}

Pipeline Queries

# List all pipelines
query GetPipelines {
  pipelines {
    id
    name
    description
    category {
      id
      name
    }
    latestVersion {
      id
      version
      canRun
    }
  }
}

# Get pipeline details
query GetPipeline($id: ID!) {
  pipeline(id: $id) {
    id
    name
    description
    versions {
      id
      version
      isLatest
      parameters {
        name
        type
        description
        required
        default
        choices
      }
    }
  }
}

# Get runnable pipelines for samples
query GetRunnablePipelines($sampleIds: [ID!]!) {
  runnablePipelines(sampleIds: $sampleIds) {
    id
    name
    category {
      name
    }
    latestVersion {
      id
      version
      parameters {
        name
        type
        required
        default
      }
    }
  }
}

Data Queries

# Get data file details
query GetData($id: ID!) {
  data(id: $id) {
    id
    filename
    size
    dataType {
      id
      name
    }
    md5
    created
    sample {
      id
      name
    }
    downloadUrl
    permissions {
      canEdit
      canShare
      canDelete
    }
  }
}

# Search data files
query SearchData(
  $pattern: String!
  $dataType: ID
  $minSize: BigInt
  $maxSize: BigInt
  $limit: Int = 50
) {
  searchData(
    pattern: $pattern
    dataType: $dataType
    minSize: $minSize
    maxSize: $maxSize
    limit: $limit
  ) {
    edges {
      node {
        id
        filename
        size
        dataType {
          name
        }
        sample {
          name
        }
      }
    }
  }
}

Mutations

Authentication Mutations

# Login
mutation Login($username: String!, $password: String!) {
  login(username: $username, password: $password) {
    user {
      id
      username
      email
    }
    accessToken
    refreshToken
  }
}

# OIDC Login
mutation OIDCLogin($idToken: String!) {
  oidcLogin(idToken: $idToken) {
    user {
      id
      username
      email
    }
    accessToken
    refreshToken
  }
}

# Logout
mutation Logout {
  logout {
    success
  }
}

# Refresh token
mutation RefreshToken {
  refreshToken {
    accessToken
  }
}

Sample Mutations

# Create sample
mutation CreateSample(
  $name: String!
  $organism: ID!
  $sampleType: ID!
  $project: ID
  $metadata: JSON
) {
  createSample(
    name: $name
    organism: $organism
    sampleType: $sampleType
    project: $project
    metadata: $metadata
  ) {
    id
    name
    created
  }
}

# Update sample
mutation UpdateSample(
  $id: ID!
  $name: String
  $condition: String
  $replicateGroup: String
  $metadata: JSON
) {
  updateSample(
    id: $id
    name: $name
    condition: $condition
    replicateGroup: $replicateGroup
    metadata: $metadata
  ) {
    id
    name
    modified
  }
}

# Share sample
mutation ShareSample(
  $id: ID!
  $users: [ID!]
  $groups: [ID!]
  $permission: PermissionLevel!
) {
  shareSample(
    id: $id
    users: $users
    groups: $groups
    permission: $permission
  ) {
    id
    sharedWith {
      ... on User {
        id
        username
      }
      ... on Group {
        id
        name
      }
    }
  }
}

# Delete sample
mutation DeleteSample($id: ID!) {
  deleteSample(id: $id) {
    success
  }
}

Project Mutations

# Create project
mutation CreateProject(
  $name: String!
  $description: String
  $organism: ID!
  $isPublic: Boolean = false
) {
  createProject(
    name: $name
    description: $description
    organism: $organism
    isPublic: $isPublic
  ) {
    id
    name
    created
  }
}

# Update project
mutation UpdateProject(
  $id: ID!
  $name: String
  $description: String
  $isPublic: Boolean
) {
  updateProject(
    id: $id
    name: $name
    description: $description
    isPublic: $isPublic
  ) {
    id
    name
    modified
  }
}

# Add samples to project
mutation AddSamplesToProject($projectId: ID!, $sampleIds: [ID!]!) {
  addSamplesToProject(projectId: $projectId, sampleIds: $sampleIds) {
    id
    sampleCount
  }
}

Execution Mutations

# Run pipeline
mutation RunPipeline(
  $pipelineVersionId: ID!
  $name: String!
  $samples: [ID!]
  $filesets: [ID!]
  $parameters: JSON
) {
  runPipeline(
    pipelineVersionId: $pipelineVersionId
    name: $name
    samples: $samples
    filesets: $filesets
    parameters: $parameters
  ) {
    id
    name
    status
    created
  }
}

# Cancel execution
mutation CancelExecution($id: ID!) {
  cancelExecution(id: $id) {
    id
    status
  }
}

# Share execution
mutation ShareExecution(
  $id: ID!
  $users: [ID!]
  $groups: [ID!]
  $permission: PermissionLevel!
) {
  shareExecution(
    id: $id
    users: $users
    groups: $groups
    permission: $permission
  ) {
    id
    sharedWith {
      ... on User {
        username
      }
      ... on Group {
        name
      }
    }
  }
}

Data Mutations

# Upload data (get upload URL)
mutation InitiateUpload(
  $filename: String!
  $size: BigInt!
  $md5: String
  $sampleId: ID
) {
  initiateUpload(
    filename: $filename
    size: $size
    md5: $md5
    sampleId: $sampleId
  ) {
    uploadId
    uploadUrl
    chunkSize
  }
}

# Confirm upload completion
mutation CompleteUpload($uploadId: ID!) {
  completeUpload(uploadId: $uploadId) {
    id
    filename
    size
  }
}

# Update data metadata
mutation UpdateData($id: ID!, $metadata: JSON) {
  updateData(id: $id, metadata: $metadata) {
    id
    metadata
  }
}

Bulk Operations

# Bulk delete samples
mutation BulkDeleteSamples($ids: [ID!]!) {
  bulkDeleteSamples(ids: $ids) {
    deletedCount
    errors {
      id
      error
    }
  }
}

# Bulk share
mutation BulkShare(
  $resourceType: ResourceType!
  $ids: [ID!]!
  $users: [ID!]
  $groups: [ID!]
  $permission: PermissionLevel!
) {
  bulkShare(
    resourceType: $resourceType
    ids: $ids
    users: $users
    groups: $groups
    permission: $permission
  ) {
    sharedCount
    errors {
      id
      error
    }
  }
}

Subscriptions

Real-time updates via WebSocket subscriptions.

Execution Updates

# Subscribe to execution status changes
subscription ExecutionUpdates($id: ID!) {
  executionUpdates(id: $id) {
    id
    status
    progress
    logs {
      timestamp
      process
      message
    }
  }
}

# Subscribe to all running executions
subscription RunningExecutions {
  runningExecutions {
    id
    name
    status
    progress
    owner {
      username
    }
  }
}

Data Upload Progress

# Subscribe to upload progress
subscription UploadProgress($uploadId: ID!) {
  uploadProgress(uploadId: $uploadId) {
    uploadId
    bytesUploaded
    totalBytes
    percentComplete
    status
  }
}

System Notifications

# Subscribe to user notifications
subscription UserNotifications {
  userNotifications {
    id
    type
    message
    timestamp
    data
  }
}

Error Handling

GraphQL errors follow a consistent format:

{
  "errors": [
    {
      "message": "Sample not found",
      "extensions": {
        "code": "NOT_FOUND",
        "id": "12345"
      },
      "path": ["sample"],
      "locations": [{"line": 2, "column": 3}]
    }
  ],
  "data": null
}

Common Error Codes

  • UNAUTHENTICATED - Invalid or missing authentication
  • FORBIDDEN - Insufficient permissions
  • NOT_FOUND - Resource not found
  • VALIDATION_ERROR - Invalid input data
  • INTERNAL_ERROR - Server error

Error Handling Example

try {
  const response = await graphqlClient.query({
    query: GET_SAMPLE,
    variables: { id: sampleId }
  });
  
  return response.data.sample;
} catch (error) {
  if (error.graphQLErrors?.length > 0) {
    const gqlError = error.graphQLErrors[0];
    
    switch (gqlError.extensions?.code) {
      case 'NOT_FOUND':
        console.error(`Sample ${sampleId} not found`);
        break;
      case 'FORBIDDEN':
        console.error('No permission to access sample');
        break;
      default:
        console.error('GraphQL error:', gqlError.message);
    }
  } else if (error.networkError) {
    console.error('Network error:', error.networkError);
  }
}

Pagination

Flow uses cursor-based pagination for GraphQL connections:

query GetSamplesPage($cursor: String, $limit: Int = 20) {
  samples(after: $cursor, first: $limit) {
    edges {
      cursor
      node {
        id
        name
      }
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
    }
    totalCount
  }
}

Pagination Example

async function* getAllSamples(client) {
  let cursor = null;
  let hasNextPage = true;
  
  while (hasNextPage) {
    const response = await client.query({
      query: GET_SAMPLES_PAGE,
      variables: { cursor, limit: 50 }
    });
    
    const { edges, pageInfo } = response.data.samples;
    
    for (const edge of edges) {
      yield edge.node;
    }
    
    cursor = pageInfo.endCursor;
    hasNextPage = pageInfo.hasNextPage;
  }
}

// Usage
for await (const sample of getAllSamples(client)) {
  console.log(sample.name);
}

Filtering and Sorting

Filter Input Types

input SampleFilter {
  organism: ID
  sampleType: ID
  project: ID
  createdAfter: DateTime
  createdBefore: DateTime
  search: String
}

input ExecutionFilter {
  pipeline: ID
  status: ExecutionStatus
  createdAfter: DateTime
  createdBefore: DateTime
}

Sorting

enum SampleSortField {
  NAME
  CREATED
  MODIFIED
}

enum SortOrder {
  ASC
  DESC
}

query GetSortedSamples(
  $filter: SampleFilter
  $sortBy: SampleSortField = CREATED
  $sortOrder: SortOrder = DESC
) {
  samples(
    filter: $filter
    sortBy: $sortBy
    sortOrder: $sortOrder
  ) {
    edges {
      node {
        id
        name
        created
      }
    }
  }
}

Batch Queries

Combine multiple queries in a single request:

query GetDashboardData($userId: ID!) {
  user(id: $userId) {
    id
    username
    ownedSamples {
      totalCount
    }
    ownedProjects {
      totalCount
    }
  }
  
  recentExecutions: executions(
    limit: 5
    sortBy: CREATED
    sortOrder: DESC
  ) {
    edges {
      node {
        id
        name
        status
      }
    }
  }
  
  runningExecutions: executions(
    status: RUNNING
  ) {
    totalCount
  }
}

Performance Tips

1. Request Only Needed Fields

# Bad - requests all fields
query GetSample($id: ID!) {
  sample(id: $id)
}

# Good - requests specific fields
query GetSample($id: ID!) {
  sample(id: $id) {
    id
    name
    organism {
      name
    }
  }
}

2. Use Fragments for Reusable Fields

fragment SampleBasicInfo on Sample {
  id
  name
  organism {
    name
  }
  sampleType {
    name
  }
}

query GetSampleWithData($id: ID!) {
  sample(id: $id) {
    ...SampleBasicInfo
    data {
      edges {
        node {
          id
          filename
          size
        }
      }
    }
  }
}

3. Implement DataLoader Pattern

For Python backends using graphene:

from promise import Promise
from promise.dataloader import DataLoader

class SampleLoader(DataLoader):
    def batch_load_fn(self, sample_ids):
        samples = Sample.objects.filter(id__in=sample_ids)
        sample_map = {s.id: s for s in samples}
        return Promise.resolve([
            sample_map.get(sid) for sid in sample_ids
        ])

# In resolver
def resolve_sample(self, info, id):
    return info.context.sample_loader.load(id)

4. Use Persisted Queries

For frequently used queries, use persisted queries to reduce bandwidth:

const PERSISTED_QUERIES = {
  GetSample: 'sha256:abcd1234...',
  GetProject: 'sha256:efgh5678...'
};

// Send only query ID
const response = await client.query({
  queryId: PERSISTED_QUERIES.GetSample,
  variables: { id: sampleId }
});

GraphQL Client Setup

Apollo Client (JavaScript)

import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
  uri: 'https://api.flow.bio/graphql'
});

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('access_token');
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : ""
    }
  };
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});

Python GraphQL Client

from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport

# Configure transport with auth
transport = RequestsHTTPTransport(
    url='https://api.flow.bio/graphql',
    headers={'Authorization': f'Bearer {token}'},
    use_json=True
)

# Create client
client = Client(
    transport=transport,
    fetch_schema_from_transport=True
)

# Execute query
query = gql('''
    query GetSample($id: ID!) {
        sample(id: $id) {
            id
            name
            organism {
                name
            }
        }
    }
''')

result = client.execute(query, variable_values={'id': '123'})
print(result['sample'])

Schema Introspection

Query the schema for available types and fields:

# Get all types
query GetSchema {
  __schema {
    types {
      name
      kind
      description
    }
  }
}

# Get fields for a type
query GetSampleFields {
  __type(name: "Sample") {
    name
    fields {
      name
      type {
        name
        kind
      }
      description
    }
  }
}

# Get available queries
query GetQueries {
  __schema {
    queryType {
      fields {
        name
        description
        args {
          name
          type {
            name
          }
        }
      }
    }
  }
}

Next Steps

Previous
REST API Reference