Skip to main content
The Connect API provides powerful filtering capabilities to query and retrieve exactly the data you need. Filters are passed as JSON-encoded objects in the filter query parameter.

Overview

Filters are used primarily with the GET /requests endpoint to narrow down the list of forms returned. All filter parameters are optional and can be combined to create complex queries.

Basic Usage

const filter = {
  flow: { slug: 'client-onboarding' },
  active: true,
  completed: false
};

const url = `https://connect.penbox.io/v1/requests?filter=${encodeURIComponent(JSON.stringify(filter))}`;

const response = await fetch(url, {
  headers: { 'Authorization': `Bearer ${accessToken}` }
});

const requests = await response.json();

Available Filters

Workspace Filtering

Filter forms by workspace (company) using the workspace slug:
{
  "company": {
    "slug": "acme-corp"
  }
}
Use cases:
  • Multi-tenant applications where each customer has their own workspace
  • Filtering forms for specific organizations
  • Workspace-specific reporting
Example:
// Get all forms from a specific workspace
const filter = {
  company: { slug: 'acme-corp' }
};

Form Template Filtering

Filter by form template (flow) using the template slug:
{
  "flow": {
    "slug": "client-onboarding"
  }
}
Use cases:
  • Display forms from a specific template
  • Template-specific analytics
  • Workflow-based filtering
Example:
// Get all client onboarding forms
const filter = {
  flow: { slug: 'client-onboarding' }
};

User Filtering

Filter forms by the contact’s information:
{
  "user": {
    "email": "[email protected]",
    "given_name": "John",
    "family_name": "Doe",
    "phone": "+1234567890",
    "internal_ref": "CRM-12345",
    "company_name": "Acme Corp"
  }
}
Available user fields:
  • email: Contact’s email address
  • given_name: Contact’s first name
  • family_name: Contact’s last name
  • phone: Contact’s phone number
  • internal_ref: Your internal reference ID
  • company_name: Contact’s company name
Use cases:
  • Find all forms sent to a specific contact
  • Customer-specific dashboards
  • Support lookup by email or phone
Example:
// Find all forms for a specific email
const filter = {
  user: { email: '[email protected]' }
};

// Find forms by phone number
const filter = {
  user: { phone: '+1234567890' }
};

Owner Filtering

Filter by the form owner (the person who created/sent the form):
{
  "owner": {
    "email": "[email protected]"
  }
}
Use cases:
  • Team member performance tracking
  • Personal dashboards
  • Assignment-based filtering
Example:
// Get forms owned by a specific team member
const filter = {
  owner: { email: '[email protected]' }
};

Status Filtering

Filter forms by their lifecycle status:
{
  "active": true,
  "archived": false,
  "completed": true,
  "processed": false
}
Status Fields:
FieldTypeDescription
activebooleanForms currently active (not expired)
archivedbooleanForms that have been archived
completedbooleanForms with at least one completed response
processedbooleanForms marked as processed
Use cases:
  • Dashboard metrics (pending vs completed)
  • Archive management
  • Workflow state tracking
Examples:
// Get all active, unarchived forms awaiting completion
const filter = {
  active: true,
  archived: false,
  completed: false
};

// Get completed but not yet processed forms
const filter = {
  completed: true,
  processed: false
};

// Get archived forms for reporting
const filter = {
  archived: true
};

External Args Filtering

Filter by your custom metadata stored in external_args:
{
  "external_args": {
    "order_id": "ORD-12345",
    "source": "crm",
    "campaign": "summer-2024"
  }
}
Use cases:
  • Link forms to your system’s entities (orders, tickets, leads)
  • Campaign tracking
  • Source attribution
  • Custom categorization
Example:
// Find form by your order ID
const filter = {
  external_args: { order_id: 'ORD-12345' }
};

// Filter by multiple custom fields
const filter = {
  external_args: {
    source: 'website',
    campaign: 'summer-2024'
  }
};
Use external_args to create bidirectional links between Penbox forms and your system’s data model.

Combining Filters

All filter criteria can be combined for precise queries:
// Complex filter: Active onboarding forms from Acme Corp that are completed but not processed
const filter = {
  company: { slug: 'acme-corp' },
  flow: { slug: 'client-onboarding' },
  active: true,
  completed: true,
  processed: false
};

// Filter by user and external reference
const filter = {
  user: { email: '[email protected]' },
  external_args: { crm_id: '12345' }
};

Pagination

Control the number of results and implement pagination:
const params = new URLSearchParams({
  filter: JSON.stringify({
    flow: { slug: 'onboarding' },
    active: true
  }),
  'page[limit]': '50',  // Max 100
  'page[offset]': '0'
});

const response = await fetch(
  `https://connect.penbox.io/v1/requests?${params}`,
  { headers: { 'Authorization': `Bearer ${accessToken}` } }
);
Pagination Parameters:
ParameterTypeDefaultMaxDescription
page[limit]integer30100Number of results per page
page[offset]integer0-Number of results to skip
The maximum page[limit] is 100. For large datasets, implement pagination using page[offset].

Common Use Cases

Dashboard: Pending Forms

Show all forms awaiting completion:
const filter = {
  active: true,
  completed: false,
  archived: false
};

Support Lookup

Find all forms for a customer:
const filter = {
  user: { email: customerEmail }
};

Performance Metrics

Track team member activity:
const filter = {
  owner: { email: teamMemberEmail },
  completed: true
};

Integration Sync

Find unprocessed forms to sync with your system:
const filter = {
  completed: true,
  processed: false,
  external_args: { synced: false }
};

Template Analytics

Get metrics for a specific form template:
const filter = {
  flow: { slug: 'client-onboarding' },
  completed: true
};

Best Practices

Always filter by the most specific criteria first (e.g., external_args or user.email) to reduce result sets.
For queries that may return many results, always implement pagination with page[limit] and page[offset].
Cache frequently-used filter results (like “active forms”) to reduce API calls.
Establish a consistent schema for external_args across your application for easier filtering and maintenance.
Always check if the result array is empty before processing:
if (requests.length === 0) {
  console.log('No forms found matching filter');
}

Examples

Multi-Workspace Application

async function getUserForms(userId, workspaceSlug) {
  const filter = {
    company: { slug: workspaceSlug },
    external_args: { user_id: userId }
  };

  const response = await fetch(
    `https://connect.penbox.io/v1/requests?filter=${encodeURIComponent(JSON.stringify(filter))}`,
    { headers: { 'Authorization': `Bearer ${accessToken}` } }
  );

  return await response.json();
}

CRM Integration

async function syncCompletedForms(crmContactId) {
  const filter = {
    external_args: { crm_contact_id: crmContactId },
    completed: true,
    processed: false
  };

  const forms = await fetch(
    `https://connect.penbox.io/v1/requests?filter=${encodeURIComponent(JSON.stringify(filter))}`,
    { headers: { 'Authorization': `Bearer ${accessToken}` } }
  ).then(r => r.json());

  // Process each form and mark as processed
  for (const form of forms) {
    await syncToCRM(form);
    await markAsProcessed(form.id);
  }
}

Dashboard Widget

async function getDashboardStats() {
  // Get counts with different filters
  const [pending, completed, processed] = await Promise.all([
    fetch(`https://connect.penbox.io/v1/requests?filter=${JSON.stringify({ active: true, completed: false })}`),
    fetch(`https://connect.penbox.io/v1/requests?filter=${JSON.stringify({ completed: true, processed: false })}`),
    fetch(`https://connect.penbox.io/v1/requests?filter=${JSON.stringify({ processed: true })}`)
  ]).then(responses => Promise.all(responses.map(r => r.json())))
   .then(results => results.map(r => r.length));

  return { pending, completed, processed };
}

Error Handling

Invalid filters return a 400 Bad Request error:
{
  "errors": [
    {
      "status": "400",
      "title": "Invalid Filter",
      "detail": "Invalid filter parameter format"
    }
  ]
}
Common issues:
  • Invalid JSON format in filter parameter
  • Unrecognized filter fields
  • Type mismatches (e.g., string instead of boolean)
Solution:
try {
  const response = await fetch(url, { headers });
  
  if (!response.ok) {
    const error = await response.json();
    console.error('Filter error:', error);
    return [];
  }
  
  return await response.json();
} catch (error) {
  console.error('Request failed:', error);
  return [];
}

Next Steps