Tinder API Style Guide — Part 1

Tinder
Tinder Tech Blog
Published in
12 min readSep 6, 2024

Authored by: Nishant Mittal

Advisory Group: Vijaya Vangapandu, Devin Thomson, Serge Vartanov and Greg Giacovelli

API Style Guide Contributors: Nishant Mittal, Xing Wei and Felix Changoo

Over the past decade, Tinder has experienced exponential user growth. Today, the app processes over 1 billion Likes and Nopes per day. This rapid expansion has been accompanied by the introduction of numerous innovative experiences, such as AI Photo Selector, MatchMaker, Share My Date, Photo and ID Verification, Passport, and Explore, among others.

To support this scale and complexity, Tinder has undergone a significant organizational transformation. Initially, a few teams managed core services, but the company has since evolved into a structure with specialized, domain-centric teams. These teams include Trust and Safety, Identity, Engagement, Revenue, Recommendations, Chat, Profile, MatchList, Machine Learning, and Infrastructure.

As new teams were established to develop services and features within their domains, each team organically developed and adopted its own design and development practices. This diversity in methodologies led to material inconsistencies across services from different domains. Below are some of the issues discussed in detail.

Inconsistent Error States

Teams operating autonomously led to inconsistency in service communication. Some used standard HTTP status codes (2xx, 4xx, 5xx), while others returned custom error codes (example: 40001, 40301, 50001, etc) within a 200 status. This confused client applications, made error handling harder, and compromised the reliability of automated monitoring and logging systems dependent on standard codes.

Lacking Schema Management

  1. Challenges in building collaboratively: We faced challenges in collaboration between backend and client teams (iOS, android, web). Backend teams often tailored schemas for their services when publishing new endpoints, making it hard for client teams to reuse them. Due to the delayed feedback loop, clients couldn’t suggest schema changes, leading them to create their own model classes.
  2. Lack of a Single Source of Truth: When clients maintained their own versions of data models, it led to the emergence of multiple, slightly different sets of models across the system. This fragmentation resulted in no single, authoritative source of truth, complicating maintenance efforts and slowing down feature releases. Additionally, the divergence between what was documented and what was actually implemented in each platform’s model created inconsistencies, leading to potential bugs, miscommunications, and a less reliable system overall.

Other issues included adoption of inconsistent versioning strategies to release new versions of APIs, unpredictable ways of designing the URI depicting vague purposes of the API and poorly designed API contracts.

To address these inconsistencies, it became essential to lay down comprehensive documentation to standardize API development practices and enforce the adoption of these standard practices through manual and systematic audits.

Tinder API Standards

Almost all Tinder endpoints are designed as RESTful APIs (Representational State Transfer), allowing for scalable and flexible interactions across our platform. In this post, we’ll explore the key standards we follow to create robust, RESTful APIs.

Designing URIs (Unique Resource Identifiers)

URI Pattern

URIs are essential in conveying the nature and behavior of an API, making it crucial to design them thoughtfully. We recommend following the pattern below whenever possible to ensure clear and effective URI design.

https://service-mesh-host-name/[version]/[service-name]/[resource]/[sub-resource]

Note:

  • If the API has the same service name and resource, the URI should include only one (e.g., v2/themes, NOT v2/themes/themes).
  • The URI should clearly reflect the API’s functionality. While the mentioned pattern is a good guideline, it’s not a strict rule — it’s a general approach that works for most URIs.

Path Prefix — Versioning

Versioning is a practice to manage changes to the APIs without disrupting the clients. It helps in clearly communicating the changes made allowing consumers to decide when to migrate to the latest version at their own pace.

Versioning Semantic and Strategy

At Tinder, we use versioning semantics with a path prefix (e.g., v1, v2). Our versioning strategy is to keep the last API version active until all clients migrate to the latest one. When a new version is published, create a sunset plan for the previous version, clearly communicate the timeline to clients, and ensure they are aligned with the migration schedule. Adjust the plan if needed, and continue supporting the last version until all clients have migrated.

When to publish a new Version of API?

API versioning should occur only when making breaking changes that require clients to update their code. Non-breaking changes should be included in the existing API version, allowing clients to adopt them at their discretion.

Let’s review scenarios to determine when to publish new API versions:

Scenario 1: Migration
When migrating from one service to another (e.g., serviceV1 to serviceV2), there’s no need for a new API version. Simply change the implementation while keeping the version the same, as there are no breaking changes for clients.

Scenario 2: Non-Breaking Changes
When adding a new field, method (e.g., POST, PUT), or endpoint, these are considered non-breaking changes:

  • New Field: No version increment is needed when adding a new field to an existing API.
  • New Method or Endpoint: Since no prior version exists, use /v1 as the versioning standard.

Scenario 3: Breaking Changes
Removing a required field, changing a field type, or renaming a field are breaking changes that will impact clients. In these cases, a new API version is required.

Note:

  • Versioning of the endpoint has no linking to the service version or versions of other endpoints under that service.
  • Each endpoint is independently versioned.

For example, if there are two endpoints under a service (ex: recs). Each endpoint can follow its own versioning as shown below depicting the current version of each endpoint.

  • GET /v2/recs/profiles
  • GET /v3/recs/core

In the above example, /v2/recs/profiles is on version v2 which depicts that its current version is v2 and there would have been a v1 version at some point in the past. Similarly, /v3/recs/core is on version v3 which depicts that its current version is v3 and breaking changes have been added twice to this API.

Important Versioning Guidelines

  1. Versioning for Breaking Changes: Publish a new version only for breaking changes; include non-breaking changes in the existing version.
  2. Document Version Changes: Clearly document changes in each API version through API documentation (e.g., Swagger).
  3. Communicate Migration Timelines: Communicate migration timelines to clients and keep the older version active until all clients have transitioned.

Path — Service, Resource and Sub-Resource Naming

The path of the URI includes the service name, resources and sub resources. Below example is

Important URI Path Design Guidelines

  1. Keep Path Segments Short: Ensure that URI path segments are concise and easy to understand.
  2. Use Plural Names for Resources: Represent collections with plural names (e.g., /resources). This convention signifies that the endpoint represents the entire collection of resources.

For example, performing a GET /resources will typically return the full collection. Conversely, a GET /resources/{id} will retrieve a specific resource from that collection. Additionally, POST /resources is used to add a new item to the collection, reinforcing the idea that the URI represents a group of related entities.

  1. Follow Kebab-Casing: Kebab casing enhances the readability of URIs by clearly separating words with hyphens (-). Therefore adopt kebab-casing for URI paths (e.g., word1-word2).
  2. Avoid Implementation-Specific Extensions: Do not include implementation-specific extensions in URIs (e.g., .php, .html, .py).

Below is an example of forming a URI based on above guidelines:

GET /v1/consent-management/consents/{consent-id}

In this example, v1 is the version of the endpoint, consent-management is the service name, consents is a resource. This API will retrieve a specific consent corresponding to the consent-id from the consents collection.

Use of Nouns in URIs

When designing URIs, use nouns for resources and sub-resources, not verbs. HTTP methods (GET, PATCH, POST, PUT, DELETE) already define the action (CRUD operations) on the resource. Including verbs like “get,” “update,” or “delete” in the URI makes the HTTP method redundant.

Path Suffix — Path Parameters and Query Parameters

Path Parameters

Path parameters are used to locate a resource. Below are a few examples of using path parameters.

GET /v1/passport/locations/{location-id}

In this example, passport is the service name, locations is the resource, location-id is a path parameter, and it locates a unique location. We can also have more than one path parameter in the URI.

GET /v1/passport/locations/{location-id}/profiles/{profile-id}

In this example, location-id and profile-id are both path parameters. It locates a specific profile for a particular location. Also note that locations is a resource and profiles is a sub-resource under locations.

Query Parameters

Query parameters are used mainly for providing additional attributes for querying i.e. query resources with additional conditions. Below are some examples of using query parameters.

GET /v1/users?status=active

The above example shows that we are trying to locate users who have status as active.

GET /v1/users?status=active&days=20

As shown above, we can have more than one query parameter.

Important Guidelines for Path and Query Parameters

  • Avoid Using Query Parameters to Alter Resource State: Refrain from using query parameters to change the state of a resource. For example creating a user using a query parameter is a bad example as shown here: GET /v1/users?mode=create
  • Use Query Parameters for Data Manipulation: Utilize query parameters for filtering, sorting, searching, and pagination.
  • Keep Query and Path Parameters Short: Ensure that both query and path parameters are concise and easy to understand.
  • Do Not Include Sensitive Information in Query or Path Parameters: URLs are often logged in server logs, browser history, and third-party monitoring tools. Including sensitive information in the URI can expose this data to unauthorized access and increase security risks.

HTTP Method

REST APIs enable all kinds of web applications to support all possible CRUD (create, read, update, delete) operations. Use the information below to find a suitable HTTP method for the action performed by the API.

Exception: When to Use POST instead of GET for Resource Retrieval?

In certain scenarios, teams may opt to use the POST HTTP method instead of GET for reading resource information. This approach, although unconventional for retrieval operations, is sometimes necessary to accommodate specific requirements. For clarity and self-documentation, any POST requests used for querying should append /query to their URI path, making their purpose explicit.

Scenarios for Using POST Instead of GET

Case 1: Complex or Large Query Data

When the query parameters are complex or voluminous, a GET request might not be suitable due to URI length limitations or the inability to include extensive data in headers. For example, retrieving information for multiple resources in a single request or passing complex nested objects might exceed the practical limits of a URI. In such cases, a POST request is more appropriate, as it allows the data to be included in the request body, bypassing the constraints imposed by GET.

Case 2: Sensitive Information Handling

GET requests append query parameters to the URI, which can expose sensitive information. Since URIs are often logged by servers or intermediaries, including sensitive information in a GET request could lead to security risks. To mitigate this, a POST request can be used, placing the sensitive query data within the request body instead of the URI, thereby reducing exposure. In these cases, combining the POST method with a /query path ensures that the intent remains clear, even though the operation is primarily retrieving data.

Best Practices

  • URI Length: Consider the maximum URI length supported by different browsers and servers when deciding between GET and POST.
  • Idempotence: Remember that while GET requests are idempotent by definition, POST requests are not inherently idempotent. Design your API in a way that ensures idempotent behavior if necessary for the client’s use case.

Exception: When to use POST instead of DELETE for Resource Deletion?

According to the latest updates in the RFC 7231, Section 4.3.5, the DELETE method is not recommended to carry a request body. This limitation can pose challenges when a deletion operation requires the client to specify complex criteria or additional information that cannot be conveyed via URI parameters alone. In such cases, leveraging the POST method can be a practical alternative.

Scenarios for Using POST Instead of DELETE

When a DELETE operation necessitates passing complex data, such as a detailed object that defines specific conditions for the deletion, a POST request should be used instead. To clearly indicate the intent of the operation, it is recommended to append /deletion to the URI path. This convention makes the operation self-documenting, allowing developers to easily understand that the endpoint performs a deletion-like action.

Best Practices

  • Idempotence: While DELETE operations are generally idempotent, POST requests are not. Ensure that the deletion logic behind the POST request can handle repeated requests without unintended side effects.
  • URI Path Naming: Consistently using the /deletion suffix for such operations helps maintain clarity across your API, making it easier for developers to understand and use your endpoints correctly.
  • Documentation: Clearly document the rationale for using POST instead of DELETE, including the structure of the expected request body and any special behavior associated with the /deletion endpoint.

Request Headers

Standard Request Headers

Standard request headers are a set of predefined headers that are widely used in RESTful APIs over HTTP to convey important metadata and control the behavior of both the client and server during the request-response cycle. These headers facilitate a consistent and predictable interaction between the client and server, ensuring that both parties adhere to the expected protocols and standards.

Below are some examples of standard headers:

Custom Request Headers

Custom headers are non-standard HTTP headers that are specific to a system, application, or organization. These headers are typically used to transmit application-specific metadata or control information that isn’t covered by the standard HTTP headers. Custom headers allow developers to extend the capabilities of HTTP, tailoring it to meet the specific needs of their applications.

Naming Conventions for Custom Headers at Tinder

At Tinder, we follow a structured naming convention for custom headers by prefixing them with X-Tinder-. This practice ensures that our custom headers are easily identifiable, reducing the risk of conflicts with future standardized headers and maintaining consistency across our API implementations.

Rationale behind using custom headers

  1. Avoiding Conflicts: By using a unique prefix like X-Tinder-, we ensure that our custom headers do not collide with standard headers or those used by other applications or frameworks.
  2. Self-Documentation: The X-Tinder- prefix immediately indicates that the header is a custom one, making it easier for developers working with the API to understand its origin and purpose.
  3. Enhanced Functionality: Custom headers allow Tinder to implement features or carry metadata that are not supported by standard headers. For example, custom headers can be used for tracking, custom authentication mechanisms, feature flags, or version control.

Below are some examples of custom headers:

Request Tracing Standards

At Tinder, we’ve standardized request tracing by generating a unique identifier for each request and injecting that as a custom header. The Tracer module in the shared repository uses Mapped Diagnostic Context (MDC) to populate these headers. Our in-house logging library then captures application-specific logs and trace IDs using these MDC trace variables.

In Part 1 of this blog, we examined the challenges Tinder encountered in maintaining their API ecosystem, along with the standards they apply to URI design, HTTP methods, and request headers. In Part 2, we’ll delve into Tinder’s API standards concerning request and response bodies, status codes, and the tools and methods used to enforce these standards, ensuring consistency across all Tinder APIs.

Editor’s Note: In this article, we explore the challenges Tinder faces in building and maintaining robust and scalable APIs, emphasizing the importance of adhering to API standards. We’ll discuss the specific standards Tinder follows and the tools and methods we use to ensure these standards are consistently applied.

We’ve divided this exploration into two parts. In Part 1, we’ll address the challenges related to maintaining APIs, focusing on standards around URI design, HTTP methods, and request headers. Part 2 will delve into standards concerning request/response bodies and status codes, along with the tools and methods Tinder employs to enforce these standards. Whether you are an engineer, a tech lead, or an executive, understanding these principles will provide valuable insights into the critical role of API standards in today’s fast-paced technological landscape.

--

--