
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.
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.
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.
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.
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:
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.
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.
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:
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.
When adding a new field, method (e.g., POST, PUT), or endpoint, these are considered non-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:
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.
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.
The path of the URI includes the service name, resources and sub resources. Below example is
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.
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.
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 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 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.
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.

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.
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.
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.
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.
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 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.
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.
Below are some examples of custom headers:

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.