Articles

Asana API: We Built a Custom Integration. Then We Stopped Maintaining It.

Ibby SyedIbby Syed, Founder, Cotera
8 min readMarch 8, 2026

Asana API: We Built a Custom Integration. Then We Stopped Maintaining It.

Asana API: We Built a Custom Integration. Then We Stopped Maintaining It.

Tomás is our most capable engineer and I should not have let him spend three weeks building an Asana reporting integration. That's on me. He volunteered, he was excited about it, and the result was genuinely good. For about three months.

The project started because our ops team needed a dashboard that showed task completion rates, overdue counts, and assignee workloads across all 18 of our active Asana projects. Asana's native reporting couldn't do this. The Advanced Search feature got close, but it couldn't calculate rates or trends. The built-in status updates were per-project and manual. We wanted one screen with everything.

Tomás said he could build it in a week using the Asana API. It took three weeks. Then it took about four hours a month to keep running. Then we replaced it with an agent in an afternoon.

The Asana API Is Well-Designed and Exhausting

The Asana REST API is honestly one of the better-documented APIs I've worked with. The resources are clearly defined: tasks, projects, sections, tags, users, custom fields, stories (comments and activity). Authentication supports both Personal Access Tokens for internal tools and OAuth for multi-user apps. The data model maps cleanly to what you see in the Asana UI.

Tomás started with a simple plan: hit the projects endpoint to list all projects, then for each project, list the tasks, then aggregate the data. The first version worked in about a day and a half.

Then the real world showed up.

Pagination was the first surprise. Asana's API returns paginated results with a default limit of 20 items per page. Our larger projects had 200+ tasks. To get all tasks in a project, you have to follow the next_page cursor through multiple requests. For 18 projects with an average of 120 tasks each, the initial data pull required roughly 110 API calls. Not a problem in isolation, but it meant the dashboard took about 45 seconds to load.

Rate limiting was the second issue. Asana enforces rate limits of approximately 150 requests per minute per user token. Our 110-call data pull was fine on its own, but Tomás also wanted to pull custom field values, assignee details, and section information. Each of those required additional API calls. The full data set required about 300 requests, which pushed us right up against the rate limit. He had to add request throttling and retry logic with exponential backoff. That was another two days of work.

The opt-in fields pattern was the third complication. By default, Asana's API returns a compact representation of each resource. To get custom fields, you have to explicitly request them with the opt_fields parameter. Tomás initially missed this and spent half a day debugging why custom field data was null. The documentation explains this clearly, but when you're moving fast, it's easy to miss.

Webhooks were the fourth rabbit hole. Instead of polling every project on a schedule, Tomás wanted real-time updates. Asana supports webhooks, but they require a publicly accessible endpoint to receive callbacks, a handshake protocol to verify the webhook, and logic to handle the various event types (task created, task updated, task deleted, project membership changed). Setting up the webhook receiver, handling the X-Hook-Secret verification, and parsing the event payloads took another three days.

The total build time: three weeks. The result: a dashboard that showed cross-project metrics in near real-time, with historical trend data stored in our database.

Three Months of Maintenance

The dashboard launched and the team loved it. For about twelve weeks. Then the maintenance started.

In month one, Asana updated their API's handling of completed tasks. A query parameter that previously returned all tasks now excluded completed tasks by default unless you explicitly set completed_since to a date far in the past. Our completion rate calculations silently broke because they were counting zero completed tasks. Tomás noticed after a week when someone pointed out that the completion rate had been showing 0% for every project.

In month two, we added three new Asana projects. Each one needed to be manually added to the integration's configuration. Tomás had hardcoded the project GIDs in the initial version. He refactored to auto-discover projects by workspace, which required changing the data pipeline and updating the aggregation logic.

Also in month two, the webhook endpoint went down during a deployment. We use a containerized deployment and the webhook receiver was part of the main application. During the 90-second deploy window, Asana sent webhook events that got 503 responses. Asana retried them, but some events were lost. The dashboard showed a gap in data for that afternoon. Tomás added a separate reconciliation job that polled the API every hour to catch anything the webhooks missed.

In month three, a team member changed a custom field name from "Priority" to "Task Priority." The dashboard broke because it was matching on the field name string. Tomás added a lookup-by-GID fallback. Another half day.

None of these individual issues was catastrophic. Each one took between two hours and half a day to fix. But the pattern was clear: the integration required ongoing attention from our best engineer, pulling him away from product work to fix a reporting tool.

The Moment We Stopped

The conversation happened during a sprint planning session. Tomás had three product tasks assigned to him and a backlog item to fix the dashboard's handling of subtasks (it was counting them as separate tasks in the completion metrics, inflating our numbers). He said: "I can either ship the feature we promised customers or fix the dashboard. I can't do both this sprint."

That's when I realized the real cost wasn't the build time. It was the ongoing opportunity cost of having Tomás maintain something that wasn't our product. Every hour he spent debugging API changes or patching webhook gaps was an hour he wasn't spending on the software we sell.

We looked at alternatives. Zapier could handle some of the data pipeline, but the aggregation and calculation logic would still need custom code. A BI tool like Metabase could query the data, but we'd still need the data pipeline to get Asana data into a database. Every option still had a maintenance surface.

Then we tried a cross-project dependency tracker agent. It reads from all projects in our workspace, pulls task data including custom fields and assignees, computes the metrics we needed (completion rates, overdue counts, workload distribution, dependency chains), and outputs a report. No webhooks. No database. No deployment pipeline. The agent queries the API each time it runs, reasons about the data, and delivers the result.

The setup took an afternoon. Priya configured the agent's prompt to match the format the team had gotten used to from Tomás's dashboard. The agent runs on a schedule and posts to Slack. It also runs on demand when someone asks for a current snapshot.

What the Agent Handles That the Integration Didn't

The original dashboard was a read-only view of aggregated metrics. It showed numbers. It didn't interpret them.

The agent does both. When it detects that Project X has three overdue tasks all assigned to the same person, it doesn't just show a red number. It notes that the assignee also has seven tasks due in the next three days across two other projects and flags a workload problem. When it finds a dependency between projects where the upstream task is late, it identifies the downstream tasks that are at risk and reports the potential schedule impact.

Tomás's dashboard couldn't do this because interpreting data requires logic that goes beyond SQL queries and chart rendering. The dashboard could show you that something was late. The agent can tell you what the lateness means and what's likely to happen next.

The agent also adapts automatically when projects change. New project added to the workspace? The agent discovers it on the next run. Custom field renamed? The agent reads field names dynamically. It doesn't rely on hardcoded GIDs or string-matched field names. The maintenance issues that consumed Tomás's time every month simply don't apply.

Build vs Buy: The Honest Calculus

If you're a developer evaluating whether to build a custom Asana integration, here's the framework I wish I'd used.

Build it yourself if the integration is your product. If you're building a tool that other people will use and Asana connectivity is a feature you're selling, the build cost is product development, not overhead. You need full control over the data pipeline, the error handling, and the user experience. The maintenance is part of the product roadmap.

Don't build it yourself if the integration is internal tooling. If you're building a dashboard for your own team's reporting, or a data pipeline that feeds your own processes, the build cost is overhead. Every hour of maintenance is an hour not spent on your actual product. The calculus gets worse over time because APIs change, team needs evolve, and the person who built it will eventually be needed elsewhere.

The middle ground is agents. An agent uses the same Asana API that a custom integration would use. It handles authentication, pagination, and rate limiting. But it doesn't require a deployment pipeline, a database, webhook infrastructure, or ongoing code maintenance. The "integration" is a prompt that describes what data to pull and what to do with it. When requirements change, you update the prompt, not the code.

Tomás's retrospective was characteristically direct: "I built something good. I'd build it again for a customer. I wouldn't build it again for us. The agent does 90% of what my dashboard did with 0% of the maintenance. The missing 10% is the pretty charts, and I'll trade pretty charts for my weekends."


Try These Agents

For people who think busywork is boring

Build your first agent in minutes with no complex engineering, just typing out instructions.