Every meeting, email thread, and Slack conversation generates action items — and most of them never make it to Asana because the manual transfer step is friction enough to skip. AI agents connected to Asana eliminate this friction by automatically converting unstructured communication into organized, assigned, deadline-bound tasks.
For project managers and team leads handling high-volume coordination, Asana AI integration can reduce the project administration overhead that currently eats deep work time.
What AI Agents Can Do With Asana Access#
Task Creation and Management
- Extract action items from meeting notes, emails, and Slack messages and create structured tasks
- Assign tasks to team members based on workload, skill match, or project ownership rules
- Set realistic due dates based on project timeline and dependencies
- Organize tasks into correct projects and sections automatically
Project Intelligence
- Generate weekly project status reports from task completion rates
- Identify at-risk projects with high proportions of overdue or blocked tasks
- Flag team members with unsustainable workloads before burnout occurs
- Surface upcoming deadlines across all projects for executive briefings
Workflow Automation
- Create recurring task sets from project templates
- Move tasks between sections based on status changes
- Archive completed projects and extract learnings for the next cycle
- Sync task status to external systems (Jira, Linear, client portals)
Setting Up Asana API Access#
pip install asana langchain langchain-openai python-dotenv
Generate your Personal Access Token from My Profile Settings → Apps → Developer Apps → New Access Token.
export ASANA_ACCESS_TOKEN="your-asana-access-token"
export ASANA_WORKSPACE_GID="your-workspace-gid"
Test the connection:
import asana
import os
configuration = asana.Configuration()
configuration.access_token = os.getenv("ASANA_ACCESS_TOKEN")
api_client = asana.ApiClient(configuration)
workspaces_api = asana.WorkspacesApi(api_client)
workspaces = workspaces_api.get_workspaces({})
for ws in workspaces:
print(ws) # Lists your workspaces
Option 1: No-Code with n8n#
Meeting Notes to Tasks Workflow#
- Webhook Trigger: Receive meeting transcript via POST from Otter.ai, Fireflies, or Notion
- OpenAI: "Extract all action items from this meeting transcript. For each, identify: task name, assignee name, due date, priority."
- Code node: Parse JSON response into individual task objects
- Asana node: Create task for each action item in the correct project
- Slack: Post task creation summary to the team channel
Project Health Check (Scheduled)#
- Schedule Trigger: Every Friday at 4pm
- Asana: Fetch all tasks in active projects
- Code node: Calculate overdue rate, completion rate per project
- OpenAI: "Generate a brief project health summary highlighting at-risk projects and team wins"
- Email: Send digest to project leads
Option 2: LangChain with Python#
Build Asana Tools#
import os
import asana
from datetime import datetime, timedelta
from langchain.tools import tool
from dotenv import load_dotenv
load_dotenv()
configuration = asana.Configuration()
configuration.access_token = os.getenv("ASANA_ACCESS_TOKEN")
api_client = asana.ApiClient(configuration)
WORKSPACE_GID = os.getenv("ASANA_WORKSPACE_GID")
tasks_api = asana.TasksApi(api_client)
projects_api = asana.ProjectsApi(api_client)
users_api = asana.UsersApi(api_client)
@tool
def list_projects() -> str:
"""List all active projects in the Asana workspace."""
projects = projects_api.get_projects({"workspace": WORKSPACE_GID, "archived": False})
result = ["Active projects:"]
for project in projects:
result.append(f" - {project['name']} (GID: {project['gid']})")
return "\n".join(result) if len(result) > 1 else "No active projects found"
@tool
def get_project_tasks(project_gid: str, completed: bool = False) -> str:
"""Get tasks from a specific Asana project. Set completed=True to include finished tasks."""
tasks = tasks_api.get_tasks_for_project(
project_gid,
{"completed_since": "now" if not completed else "1970-01-01",
"opt_fields": "name,assignee.name,due_on,completed,notes"}
)
task_list = list(tasks)
if not task_list:
return f"No {'incomplete' if not completed else ''} tasks in project {project_gid}"
result = [f"Tasks in project ({len(task_list)} found):"]
for task in task_list[:20]:
assignee = task.get("assignee", {}).get("name", "Unassigned") if task.get("assignee") else "Unassigned"
due = task.get("due_on", "No due date")
status = "✅" if task.get("completed") else "⬜"
result.append(f" {status} {task['name']} | Assignee: {assignee} | Due: {due}")
return "\n".join(result)
@tool
def create_task(name: str, project_gid: str, assignee_email: str = None,
due_days_from_now: int = None, notes: str = "") -> str:
"""
Create a new task in an Asana project.
assignee_email: optional email of the assignee.
due_days_from_now: optional days until the due date.
"""
task_data = {
"name": name,
"notes": notes,
"projects": [project_gid],
"workspace": WORKSPACE_GID
}
if assignee_email:
# Find user by email
users = users_api.get_users({"workspace": WORKSPACE_GID, "opt_fields": "name,email"})
for user in users:
if user.get("email") == assignee_email:
task_data["assignee"] = user["gid"]
break
if due_days_from_now is not None:
due_date = (datetime.now() + timedelta(days=due_days_from_now)).strftime("%Y-%m-%d")
task_data["due_on"] = due_date
task = tasks_api.create_task({"data": task_data})
return f"Task created: '{task['name']}' (GID: {task['gid']}) in project {project_gid}"
@tool
def get_overdue_tasks(project_gid: str = None) -> str:
"""Get all overdue incomplete tasks, optionally filtered by project."""
today = datetime.now().strftime("%Y-%m-%d")
params = {
"workspace": WORKSPACE_GID,
"completed_since": "now",
"due_before": today,
"opt_fields": "name,assignee.name,due_on,projects.name"
}
if project_gid:
tasks = tasks_api.get_tasks_for_project(project_gid, {
"completed_since": "now",
"opt_fields": "name,assignee.name,due_on"
})
else:
tasks = tasks_api.get_tasks(params)
overdue_list = list(tasks)
if not overdue_list:
return "No overdue tasks found"
result = [f"Overdue tasks ({len(overdue_list)} total):"]
for task in overdue_list[:15]:
assignee = task.get("assignee", {}).get("name", "Unassigned") if task.get("assignee") else "Unassigned"
days_overdue = (datetime.now() - datetime.strptime(task.get("due_on", today), "%Y-%m-%d")).days
result.append(f" {task['name']} | Assignee: {assignee} | {days_overdue} days overdue")
return "\n".join(result)
@tool
def update_task_status(task_gid: str, completed: bool = True, notes: str = "") -> str:
"""Mark a task as complete or incomplete. Optionally add completion notes."""
update_data = {"completed": completed}
if notes:
update_data["notes"] = notes
task = tasks_api.update_task(task_gid, {"data": update_data})
status = "completed" if completed else "reopened"
return f"Task '{task['name']}' {status} successfully"
Project Manager Agent#
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o", temperature=0.1)
tools = [list_projects, get_project_tasks, create_task,
get_overdue_tasks, update_task_status]
prompt = ChatPromptTemplate.from_messages([
("system", """You are a project management assistant with access to Asana.
When processing meeting notes or requests:
1. Parse all action items with clear owners and deadlines
2. Create tasks with specific, actionable names (verb + object, e.g., "Review API proposal")
3. Set realistic due dates based on priority and complexity
4. Always list the projects available before creating tasks
When reporting project health:
1. Check overdue tasks and quantify the backlog
2. Calculate completion rate for tasks due this week
3. Identify team members with concentrated overdue items
4. Provide concise recommendations, not lengthy reports"""),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_iterations=8)
Rate Limits and Best Practices#
| Asana API limit | Value |
|---|---|
| Requests per minute | 150 per user |
| Batch operations | Not natively supported (create individually) |
| Webhook payload size | 10MB max |
Best practices:
- Use GIDs, not names: Always reference projects, tasks, and users by GID rather than name to avoid errors from duplicate names
- Opt-fields for efficiency: Specify
opt_fieldsin every request to fetch only the fields you need — reduces response size and speeds up processing - Handle pagination: Asana responses are paginated — check for
next_page.offsetand loop to fetch all records for large projects - Test in a sandbox project: Create a dedicated test project and use it for agent development to avoid polluting active team work
Next Steps#
- AI Agents Slack Integration — Post Asana task summaries to Slack automatically
- AI Agents Linear Integration — Engineering-focused alternative to Asana
- AI Agents Airtable Integration — Use Airtable as a data layer alongside Asana tasks
- Build an AI Agent with LangChain — Complete framework tutorial