Receiving 499 status code on query

Describe the issue:

We notice that we receive a 499 status when we execute a query.

{
   "name":"ApiError",
   "response":{
      "status":499,
      "statusText":"Client Closed Request",
      "headers":{
         
      },
      "body":""
   },
   "query":"query disclaimerQuery($lang: SiteLocale, $slug: String) {\n  product(locale: $lang, filter: {slug: {eq: $slug}}) {\n    disclaimer {\n      ...ProductDisclaimer\n    }\n  }\n}\n\nfragment ProductDisclaimer on ProductDisclaimerRecord {\n  title\n  body {\n    value\n  }\n  link {\n    ...ExternalLinkBlock\n  }\n  color\n}\n\nfragment ExternalLinkBlock on ExternalLinkRecord {\n  __typename\n  label\n  url\n  openInNewWindow\n}",
   "options":{
      "graphqlEndpointUrl":"XXXXXXXXXX",
      "variables":{
         "lang":"nl_BE",
         "slug":"delabie-handgreep-basic-135-32-met-3-bevestigingspunten-rvs"
      },
      "excludeInvalid":true,
      "token":"XXXXXXXXXX",
      "requestInitOptions":{
         "cache":"no-cache"
      }
   }
}
const productDisclaimerQuery = graphql(
  `
    query disclaimerQuery($lang: SiteLocale, $slug: String) {
      product(locale: $lang, filter: { slug: { eq: $slug } }) {
        disclaimer {
          ...ProductDisclaimer
        }
      }
    }
  `,
  [ProductDisclaimer],
);

We think that when the slug is too long we receive this error. But this is just a guess.

(Recommended) What’s the URL of the record or model in question?

We don’t have a specific record because it’s a where query so record is returned based on slug. The model is product_disclaimer

Thanks for sharing the error details. A 499 “Client Closed Request” is not emitted by the DatoCMS Content Delivery API itself. It is a non‑standard status most commonly returned by proxies like Cloudflare or nginx when the client or an upstream layer aborts the request while the server is still processing it. Cloudflare documents this here: https://developers.cloudflare.com/support/troubleshooting/http-status-codes/4xx-client-error/error-499/. In practice this usually means something in front of our API closed the connection early, for example a browser navigation that cancels an in‑flight fetch, a framework level abort, or a timeout enforced by a reverse proxy.

Your GraphQL looks fine, and the slug length would not trigger a 499. Filtering by a string slug is fully supported, and if no record matches you would simply get product: null in a 200 OK response. You can see how filters work here: https://www.datocms.com/docs/content-delivery-api/filtering-records. Also, our GraphQL endpoint is always https://graphql.datocms.com/, explained here: https://www.datocms.com/docs/content-delivery-api/api-endpoints. When the CDA rejects a request, it returns a JSON error payload, not a 499 with an empty body.

To rule out the API and confirm the issue sits in a proxy or client layer, try the exact same query directly against our endpoint using our client. This goes straight to the CDA over POST and avoids any custom proxy behavior:

import { executeQuery } from '@datocms/cda-client';

// reuse your existing TypedDocumentNode if you have one
const result = await executeQuery(productDisclaimerQuery, {
  token: process.env.DATOCMS_API_TOKEN,
  variables: {
    lang: 'nl_BE',
    slug: 'delabie-handgreep-basic-135-32-met-3-bevestigingspunten-rvs',
  },
  excludeInvalid: true,
  // optional, can help prevent the browser from killing the request during unload
  requestInitOptions: { keepalive: true },
});

console.log(result);

The client above is documented here: https://github.com/datocms/cda-client. If this direct call returns 200 OK, then the 499 you are seeing is coming from the layer you currently hit via graphqlEndpointUrl in your error dump. In that case the usual culprits are a client side abort or a timeout at the proxy. If you are behind Cloudflare, review any worker or WAF in front of the endpoint and its timeouts. If you are behind nginx, 499 typically appears when the client closes the connection first, which aligns with this behavior.

Two extra checks that sometimes help. If your app navigates away or re‑triggers the same fetch, make sure an AbortController in your code or framework is not cancelling the previous request. Also, if a proxy is rewriting GraphQL queries to GET for caching, force POST. Our client uses POST by default, and you can keep using it as shown above.