# TheFork B2B API > REST API of TheFork Manager for partners such as CRM and PMS vendors, AI phone agents and restaurant groups. Base URL https://api.thefork.io/manager, OAuth2 client credentials. This file covers the URLs that start with https://docs.thefork.io/B2B-API/. **Which source to trust.** The API generates its OpenAPI schema from the deployed code, so the schema is current. Use only the schema for endpoint details: paths, parameters, request bodies, response bodies and error codes. The schema also lists the scopes with their descriptions. The Scopes paragraph below says which scopes each endpoint accepts. Do not read the pages whose URL starts with https://docs.thefork.io/B2B-API/API%20specifications/. They are snapshots of the schema and can be older than the API. Read the OpenAPI schema instead: https://api.thefork.io/manager/openapi.json. Use the Docs pages only for topics that the schema does not cover. These topics are webhooks, the glossary, versioning and the rate limit. When a page and the schema disagree about an endpoint, trust the schema. The Error Handling section of the page Event-based webhook flow shows error bodies that do not match the API error formats. Use the schema for errors. **How to read the schema.** Send a GET request to https://api.thefork.io/manager/openapi.json. A HEAD request to that URL returns 404. The schema declares OpenAPI 3.1.0, but it marks fields that can be null with nullable: true, the OpenAPI 3.0 keyword. Keep these fields nullable when you generate types. The first tag of each endpoint is its category. The second tag is a sort index, not a category. The schema has no top-level tags list and no webhooks object. **Use cases.** 1. Build your own booking funnel. Read the availabilities and timeslots of a restaurant. Then create, update and cancel reservations. 2. Receive updates. Ask TheFork to activate a webhook for your URL. TheFork sends an event when a reservation, a customer or a review changes. Then send a GET request for the entity. **Endpoints.** The schema lists every endpoint. The categories and their endpoints are: - Authentication: POST /oauth/token on the host https://auth.thefork.io. It is the only endpoint on that host. It is the only endpoint that does not need an access token. - Booking flow: GET /v1/restaurants/{id}/availabilities, GET /v1/restaurants/{id}/timeslots, GET /v1/restaurants/{id}/partySizes, GET /v1/restaurants/{id}/offers, PUT /v1/restaurants/{id}/availabilities/override, POST /v1/restaurants/{id}/reservations, PATCH /v1/reservations/{id}, PATCH /v1/reservations/{id}/cancel. - Data: GET /v1/reservations, GET /v1/reservations/{id}, GET /v1/customers, GET /v1/customers/{id}. - Review flow: GET /v1/reviews, GET /v1/reviews/{id}, PUT /v1/reviews/{id}/reply. - Phone: GET /v1/restaurants/{id}/customers/phone/{phoneNumber}, POST /v1/callCenter/callRecognitions, PATCH /v1/integrationStatus. All endpoints except POST /oauth/token use the base URL https://api.thefork.io/manager. Their request and response bodies are JSON. The schema has no DELETE endpoint. Cancel a reservation with PATCH /v1/reservations/{id}/cancel. **Access token.** Send POST https://auth.thefork.io/oauth/token with the header Content-Type: application/x-www-form-urlencoded. The body has four required fields. audience is https://api.thefork.io. grant_type is client_credentials. client_id and client_secret are the credentials that TheFork sent to you through a secure channel. The response has access_token, token_type, expires_in and scope. token_type is Bearer. expires_in is 8600 seconds. Send the header Authorization: Bearer ACCESS_TOKEN on every API request. Cache the access token. Request a new token when expires_in has elapsed. Do not request a new token while the current token is valid. **Scopes.** The schema lists the scopes under components.securitySchemes.oauth2ClientCredentials.flows.clientCredentials.scopes, each with a description. The schema does not say which scope each endpoint needs. The API gateway checks the scope before the request reaches the API. The token must have at least one of the scopes that the endpoint accepts. If it does not, the API gateway returns 403. The API gateway accepts these scopes: - GET /v1/reservations, GET /v1/reservations/{id}, POST /v1/restaurants/{id}/reservations, PATCH /v1/reservations/{id} and PATCH /v1/reservations/{id}/cancel: tfm-restaurant, tfm-restaurant:reservations or tfm-restaurant:unrestricted-reservations. - GET /v1/restaurants/{id}/availabilities, GET /v1/restaurants/{id}/timeslots and GET /v1/restaurants/{id}/partySizes: tfm-restaurant or tfm-restaurant:availabilities. - PUT /v1/restaurants/{id}/availabilities/override: tfm-restaurant:override-availabilities only. The schema describes tfm-restaurant as full access, but the API gateway does not accept tfm-restaurant on this endpoint. - GET /v1/restaurants/{id}/offers: tfm-restaurant or tfm-restaurant:offers. - GET /v1/customers, GET /v1/customers/{id} and GET /v1/restaurants/{id}/customers/phone/{phoneNumber}: tfm-restaurant or tfm-restaurant:customers. - GET /v1/reviews, GET /v1/reviews/{id} and PUT /v1/reviews/{id}/reply: tfm-restaurant or tfm-restaurant:reviews. - POST /v1/callCenter/callRecognitions and PATCH /v1/integrationStatus: tfm-restaurant or tfm-restaurant:callCenter. The scope tfm-restaurant:unrestricted-reservations also changes POST /v1/restaurants/{id}/reservations. With this scope, the API creates the reservation without a link to a published offer or availability slot. Without this scope, the reservation must match a published offer or availability slot. TheFork does not grant this scope by default. Ask your TheFork contact if you need it. **Identifiers.** All entity identifiers are UUIDs: restaurantUuid, groupUuid, reservationUuid, customerUuid, reviewUuid and offerUuid. The path parameter of an entity is named id. In the GET /v1/restaurants/{id}/offers response, the identifier field of each offer is named uuid. Send that value as offerUuid when you create a reservation. A restaurant group contains several restaurants. Customers belong to a restaurant group, not to one restaurant. The API has no endpoint that lists restaurants or restaurant groups. Get the restaurantUuid and groupUuid values from TheFork for each restaurant that you integrate. Every webhook payload carries groupUuid. Reservation and review payloads also carry restaurantUuid. No API response contains groupUuid. **Lists, then details.** GET /v1/reservations, GET /v1/customers and GET /v1/reviews return an array of UUIDs in the data field. Send a GET request to the detail endpoint for each UUID. The required parameters are: - GET /v1/reservations: startDate, endDate, and at least one of restaurantUuid and groupUuid. The schema marks both UUIDs optional and states this rule in their descriptions. The optional parameter filterBy selects the date field. updatedDate is the default. mealDate is the other value. - GET /v1/customers: groupUuid, startDate and endDate. The dates filter on the creation or last update date. - GET /v1/reviews: restaurantUuid, startDate and endDate. endDate must be later than startDate. The dates filter on the meal date. There is no groupUuid filter. GET /v1/reservations and GET /v1/customers take page and limit, and return totalCount, page and limit. GET /v1/reviews takes page and limit, and returns hasNext. Page numbers start at 1. Read the limit description in the schema for the maximum and the default. The detail endpoints return the entity as the top-level JSON object, with no data wrapper. The availabilities, timeslots, party sizes and offers endpoints return a top-level JSON array. **Dates and times.** The query parameters startDate, endDate and date use the format YYYY-MM-DD. The timezone rule is not the same for all endpoints: - GET /v1/reservations: startDate is the start of the day and endDate is the end of the day, in UTC. - GET /v1/reviews: the API reads startDate and endDate as 00:00 UTC of the given date, not as the local time of the restaurant. - GET /v1/customers: plain calendar date, no timezone conversion. - GET /v1/restaurants/{id}/availabilities, /timeslots and /partySizes: the local timezone of the restaurant. GET /v1/restaurants/{id}/offers has no date parameter. It requires locale. Read the parameter description in the schema before you build a date filter. In the reservation and timeslot responses, mealDate and datetime are ISO 8601 date-times with the UTC offset of the restaurant. The value is the local time of the restaurant. The API does not convert them to UTC. Send the datetime value from the timeslots response unchanged as mealDate. The API reads a request mealDate without a UTC offset in the local timezone of the restaurant. POST /v1/restaurants/{id}/reservations rejects a mealDate in the past. In the review responses, reservation.mealDate, createdTs and updatedTs are ISO 8601 date-times in UTC or with a UTC offset. The schema does not state the timezone of createdAt, updatedAt, creationDate and lastUpdateDate. Their examples are in UTC. **Reservation status.** A 200 response to POST does not mean that the reservation is confirmed. Read the status field. RECORDED means confirmed. REQUESTED means that the restaurant has not decided yet, for on-request bookings or the waitlist. The other values are CANCELED, NO_SHOW and REFUSED. Set your own reservation state from status. Read status again when you receive a reservationUpdated event. **Booking rules.** The POST body requires mealDate, partySize and customer. customer requires email, firstName, lastName, locale and civility. The POST body has no payment field. If the timeslot requires a payment guarantee, the API returns 400 with data.code PAYMENT_GUARANTEE_NOT_SUPPORTED. Read hasPaymentGuaranteeRequirement in the timeslots response before you book. The API records every cancellation as a cancellation by the restaurant, regardless of who requested it. **Phone.** Send phone numbers in the E.164 format in the phoneNumber path parameter, in customer.phone of POST /v1/restaurants/{id}/reservations and in callPhoneNumber of POST /v1/callCenter/callRecognitions. The pattern is ^\+[1-9]\d{1,14}$: a plus sign, a first digit from 1 to 9, then 1 to 14 more digits, with no spaces or separators. callCallerId in POST /v1/callCenter/callRecognitions is the phone number of the caller in local dialling format, not E.164. Phone fields in responses are not always in E.164 format. GET /v1/restaurants/{id}/customers/phone/{phoneNumber} matches the stored phone number by exact string equality. The stored number is not always in E.164 format, so the lookup can return 404 for a customer that exists. For an unknown phone number, the endpoint returns 404 with data.code PHONE_NUMBER_NOT_FOUND. A 200 response includes the upcoming reservations of that customer at that restaurant in nextReservationsList. PATCH /v1/integrationStatus reports one milestone of the telephony hardware onboarding. The final milestone also grants the phone partner that sends the request access to the restaurant group. **Reviews.** GET /v1/reviews lists only valid, published reviews. GET /v1/reviews/{id} still returns an invalid review, with isValid false. When isValid is false, the review does not count for the public rating and review list. Read isValid after a reviewValidityChanged event. PUT /v1/reviews/{id}/reply creates or updates the restaurant reply. It works only when comment.publicationStatus is PUBLISHED. Otherwise it returns 400 with data.code REVIEW_HAS_NO_PUBLISHED_COMMENT. The reply enters moderation. TheFork does not publish it immediately. **Status codes.** - 400: the request is malformed, or a business rule failed. data.code is a business code such as NO_AVAILABILITY, or a string that starts with INVALID_ and names the invalid field, or an empty string. - 401: the access token is invalid or expired. The API also returns 401 when the token is valid but the restaurant, group or entity does not belong to your OAuth client. In both cases the API returns error format 1 with an empty data.code. The API gateway can also return 401 in error format 2. On a 401, check the expiry of your cached token. If it has expired, request a new token and retry once. If it has not expired, do not request a new token. Check your restaurantUuid, groupUuid and entity identifiers instead. - 403: the token does not have a scope that the endpoint accepts. Do not retry. Ask TheFork to add the scope to your OAuth client. - 404: the API did not find the entity, for example RESERVATION_NOT_FOUND. Read data.code. - 409: the request conflicts with the current state, for example DOUBLE_BOOKING or DOUBLE_CANCELLATION. - 429: too many requests. Your contract sets the rate limit. The pages give no number. Retry with exponential backoff. - 500: unexpected server error. data.code is INTERNAL_SERVER_ERROR. The API also returns 500 for an upstream timeout, so the error can be temporary. Retry a GET request with exponential backoff. Check the current state before you retry a POST or PATCH. - 503: the API is temporarily unavailable. Retry with exponential backoff. **Error formats.** There are three formats. A 401 from https://api.thefork.io/manager uses format 1 or format 2, so parse both. A 401 from POST /oauth/token uses format 3. 1. From the API, for 400, 401, 404, 409 and 500. POST /oauth/token also uses this format for 500. Example: {"statusCode": 404, "error": "Not Found", "data": {"code": "RESERVATION_NOT_FOUND"}}. The machine-readable code is data.code. On 401, data.code is an empty string. 2. From the API gateway, for 401, 403, 429 and 503: {"message": "Forbidden"}. This format has no data.code. 3. From POST /oauth/token, for 401 and 403: {"error": "access_denied", "error_description": "Unauthorized"}. Detect this format by the presence of error and error_description. On 403, error is access_denied or unauthorized_client, and error_description is free text. **Versioning.** The API has one version. The path contains the version: /v1/. A breaking change creates a new version. TheFork stops support for the previous version 6 months after the release of the new version. **Webhooks.** The schema has no webhook content. The page Event-based webhook flow is the only page that documents the webhook mechanics. To set up a webhook: 1. Expose a URL that accepts POST requests. 2. Put a secret token in that URL. 3. Send that URL to TheFork. 4. TheFork configures and activates the webhook. There is no API endpoint for this. Every payload has entityType, uuid and eventType. entityType is one of customer, reservation or review. A customer payload also has groupUuid. A reservation payload also has groupUuid and restaurantUuid. A review payload also has groupUuid, restaurantUuid and reservationUuid. The payload carries identifiers. Do not expect entity data in it. The event types are customerCreated, customerUpdated, reservationCreated, reservationUpdated, reviewRatingCreated, reviewCommentPublished, reviewCommentUnpublished and reviewValidityChanged. TheFork also sends reviewCommentUnpublished when a new comment enters moderation, before reviewCommentPublished. reviewValidityChanged is rare and usually follows a data deletion request. When you receive an event: 1. Return HTTP 200 with the body {"data": {}} within a few seconds. The page gives no exact timeout. If TheFork does not receive the 200 quickly, it sends the event again. After a 200, TheFork does not send the event again. 2. Process the event after the response, for example in a background job. If your processing fails, you must recover it yourself, for example with your own retry queue. 3. If you do not know the entityType or the eventType, do not process the event further. 4. Send GET /v1/reservations/{id}, GET /v1/customers/{id} or GET /v1/reviews/{id} for the entityType. Put the uuid value in the {id} path parameter. The response is the current entity. **Example.** Get a token, then list the reservations of one restaurant filtered by mealDate. The response has data with UUIDs, totalCount, page and limit. Send GET /v1/reservations/{id} for each UUID. ```bash curl -X POST https://auth.thefork.io/oauth/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'audience=https://api.thefork.io&grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET' curl 'https://api.thefork.io/manager/v1/reservations?restaurantUuid=RESTAURANT_UUID&startDate=2026-09-01&endDate=2026-09-30&filterBy=mealDate' \ -H 'Authorization: Bearer ACCESS_TOKEN' ``` ## API reference - [OpenAPI schema](https://api.thefork.io/manager/openapi.json): the API generates this OpenAPI 3.1 JSON file. Use it as the reference for paths, parameters, bodies, error codes and scope descriptions. Send a GET request to download it. ## Docs - [Introduction](https://docs.thefork.io/B2B-API/introduction): the two use cases, the rate limit, versioning and the link to the schema. - [Authentication](https://docs.thefork.io/B2B-API/authentication): client credentials flow, token lifetime and the Authorization header. The schema covers the same facts. - [Event-based webhook flow](https://docs.thefork.io/B2B-API/event-webhook-flow): webhook activation, payload fields, event types, acknowledgement and retry rules. Its Error Handling section does not match the API error formats. - [Glossary](https://docs.thefork.io/B2B-API/glossary): definitions of access token, availability, customer, double booking, guarantee requirement, imprint, meal status, offer, partner, party size, prepayment, reservation, reservation status, restaurant group, restaurant UUID, review, stock, timeslot and webhook. ## Optional - [Best practices](https://docs.thefork.io/best-practices): general guidance for all TheFork APIs. HTTPS, retries with exponential backoff, logging, caching and pagination. - [Terms of use](https://docs.thefork.io/pdf/LaFourchette-Partners-API-Licence-2.pdf): API licence agreement, PDF.