How Calendly standardized authorization with SpiceDB

Jon CaseyJon Casey
October 28, 20269 min read

As Calendly has grown beyond scheduling into collaboration-heavy experiences like meeting recaps, shared workspaces, and AI-powered workflows, the need for a robust authorization system has increased.

At a glance, authorization sounds simple. A user wants to view, edit, share, or delete a resource, and the system needs to answer one question: is the current user permitted to do that?

However, once a product moves past single-user ownership, that becomes a harder question to answer. Access can be granted directly, inherited through organizations or groups, delegated to another person, or granted from a parent resource. And when those patterns are implemented differently across services, the cost compounds quickly.

Different teams structure their permissions tables differently. Permission logic gets split across SQL, application code, and service boundaries. Similar features behave differently depending on where they were built. Reviews become harder. Security risks increase. And every product team ends up spending time reinventing authorization infrastructure instead of building the product.

That was the backdrop for a broader shift at Calendly: standardizing authorization across products around a single relationship-based model using SpiceDB, an open-source system inspired by Google Zanzibar.

When authorization stops being a local problem

Software often begins with simple access rules: a record belongs to a user, or a team admin can manage a team resource. For these initial permission models, a small set of role-based checks is usually enough.

The trouble starts when products rapidly evolve beyond those original assumptions. A resource that used to belong to one person now needs to be shared with a teammate. Then with a group. Then with an entire organization. Then maybe with an external collaborator using a different identity model. Eventually you may have parent-child relationships, time-bound permissions, delegated access, and cross-product sharing patterns.

At that point, authorization is no longer a local implementation detail. It is now part of platform infrastructure. That realization shaped our direction. We did not want each team building its own permission model, storage pattern, inheritance rules, and migration story from scratch. Instead, we wanted a consistent way to determine authorization across resources and services.

Why a relationship-based model fit the problem

We were looking for a system built around relationships rather than scattered conditionals.

That distinction matters. In a relationship-based model, permissions are not just booleans hidden inside business logic. They emerge from explicit relationships between actors and resources. A user might own a recap, a group might be allowed to view a folder, and a person might gain access to a child resource through their access to the parent. Any service can then evaluate those relationships consistently when making decisions.

This model matched the direction we were heading for several reasons. 

First, it gives us a cleaner way to express direct access, inherited access, group-based access, and parent-child propagation without continuously redesigning resource-specific Access Control Lists (ACLs).

Second, it lets us treat authorization as a shared capability instead of a one-off implementation. Product teams can focus on what their feature needs to do, while the underlying permission model follows a more standard path.

Third, it aligns with how modern product surfaces are evolving. Once a company has multiple products, collaboration features, and service boundaries, permission models increase in complexity.

Starting off with the right use case

Standardizing a platform concern does not require migrating everything at once. With that in mind, we  deliberately chose a first production use case that delivers product value and is constrained enough to validate the model without needing to solve every authorization use case on day one.

We selected the Notetaker meeting recap sharing feature as our pilot use case. It was a strong fit because it mattered to the product and it did not immediately require one of the hardest problems in relationship-based authorization: listing every resource a user can access at scale. For the first rollout, we focused  on permission checks, rollout discipline, schema design, operational behavior, and service integration patterns.

Doing so let us test the pieces that matter in production:

  • How a service writes relationships
  • How it checks permissions
  • How to migrate safely from an existing source of truth
  • How to monitor latency and operational health
  • How to create a pattern other teams can reuse

Building a paved path, not just a pilot

Getting SpiceDB running in our cluster was only one part of the work. The pilot was also an opportunity to make future adoption easier for the teams that would follow.

We used the pilot to formalize integration guidance for teams introducing relationship-based authorization into their services. The guide turns the lessons from the first implementation into a repeatable path detailing  how to model resources and relationships, how to introduce inheritance, how to integrate permission checks, how to migrate from an existing source of truth, and how to operate the system safely.

A useful authorization platform needs more than a schema. It needs conventions that help teams make consistent decisions without starting from scratch each time.

The integration guide emphasizes a few principles:

  • Start by understanding the resource and how users can interact with it. 
  • Model direct relationships first. Add complexity only when the product actually needs it.
  • Prefer relationships over clever conditional logic. SpiceDB caveats are powerful, but they should solve the right class of problem rather than become the default escape hatch.
  • Migrate with discipline: dual-write, backfill, switch reads, and monitor performance before removing the legacy path.

The goal is to give feature teams a clear starting point, common patterns, and practical integration steps. Our goal in doing so was to reduce  the cost of adoption and help each team avoid resolving the same design and migration questions we already encountered and solved. 

This is how a pilot becomes a platform: not by making one implementation perfect, but by turning what we learned into a well documented  path that other teams can use.

Operating at production scale

In addition to validating the new authorization model, we  also needed to represent Calendly’s existing foundational relationships in SpiceDB, give teams a repeatable way to extend and integrate with the model, and make sure the system was stable, performant, and scalable in production.

Representing existing relationships

We needed to represent existing relationships and memberships in SpiceDB without interrupting the authorization behavior existing users depended on. We started by adding a dual-write path so that new relationship changes were written to both the existing source of truth and to SpiceDB. We then ran a backfill job to load existing foundational relationships and memberships, ultimately writing more than 50 million relation tuples.

This process gave us a complete dataset to validate before relying on SpiceDB for permission checks. It also gave us a repeatable migration pattern for future systems that need to move from local authorization data to a centralized relationship model.

Creating a path for team adoption

The pilot was also an opportunity to make schema changes and service integration easier for feature teams. The cluster configuration and SpiceDB schema are maintained in an infrastructure-as-code repository, where teams can test, experiment with, and modify proposed schema changes before submitting them to the IAM team for review.

We also generate SDKs from the schema that wrap the open-source SpiceDB SDKs. This simplifies setup for feature teams and gives them a consistent way to connect their services to the authorization system without managing the underlying client details themselves.

Ensuring stability, performance, and scalability

We treated SpiceDB like any other production service. Permission checks reached a p95 latency of under 40 milliseconds, and OpenTelemetry metrics are collected in Datadog so we can monitor health and performance and build alerting around the authorization path. We chose PostgreSQL as the SpiceDB datastore in part because our engineering and infrastructure teams already have deep experience operating it, and that familiarity helped us move quickly.

The biggest challenge to date surfaced after the backfill wrote more than 50 million foundational relationships. We temporarily saw a dramatic increase in latency for a subset of delete requests, and the cause was initially unclear. Datadog traces showed that a resource-wide delete path was using a query plan that did not make effective use of the resource identifier. Instead of seeking the small set of rows for one resource, PostgreSQL scanned a much larger portion of the relation table. Because SpiceDB writes at SERIALIZABLE isolation, the scan created broad predicate locks that conflicted with concurrent writes and produced 40001 serialization failures. SpiceDB retried those failures, turning a small number of deletes into a retry storm that drove PostgreSQL CPU and tail latency higher.

Once we identified the problem as a suboptimal query plan, the fix was straightforward: running ANALYZE refreshed PostgreSQL’s table statistics and allowed the planner to select a more appropriate plan. The incident reinforced the value of having full instrumentation in place — even for a relatively small pilot. With traces and database metrics in Datadog, we could follow the request across service and datastore boundaries, distinguish an authorization issue from a database planning issue, and resolve the problem without changing the authorization model itself.

What changed for engineers

The biggest change is how teams frame the problem.

When authorization is fragmented, engineers often think about access control as something buried inside controller logic, middleware, data access code, or special-case tables. Each new feature starts by deciding where to add one more rule.

Using a  standardized model changes the starting point. Rather than embedding authorization decisions in each service, teams can use a common model and integration path. This makes designs easier to review, gives engineers a clearer way to extend existing permissions, and reduces the amount of authorization infrastructure each team has to invent.

The model also creates leverage. If a team needs to introduce richer sharing later, it can extend an established pattern instead of rewriting one. If another product needs something similar, it can build on shared primitives instead of inventing a new permission system. And, when security or platform teams review the design, they are reviewing a common language instead of bespoke local logic.

The work ahead

Standardizing authorization is not a finish line. It is a foundation. The first phase proved that a relationship-based model can work in production for a real Calendly use case. The next phase is where the broader value compounds: shared patterns for organizations and groups, richer collaboration models, safer migrations for existing systems, and a consistent approach for products that need relationship-based access control from day one.

While customers may never think about authorization engines directly, they  certainly  benefit when access behavior is more consistent, systems are more secure, and product teams can deliver value faster.

At product companies, important engineering work may not be visible to the customer immediately. It is often this work that makes future product development safer, faster, and more coherent.

For Calendly, standardizing authorization is that kind of investment.