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 authenticationFORBIDDEN
- Insufficient permissionsNOT_FOUND
- Resource not foundVALIDATION_ERROR
- Invalid input dataINTERNAL_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
- API Overview - Understanding Flow's API architecture
- API Authentication - Authentication guide
- Python Client Guide - Using the flowbio library
- API Permissions - Understanding access control
- API Errors - Error handling best practices