Skip to content

Connection protocol: HTTP/2 and HTTP/3 ​

The Simulator talks to Instagram over HTTP/2 or HTTP/3 the way the app does. This page says which one each platform uses, how to force one, and how to see which one a request actually used.

Choose the protocol ​

The Android process uses HTTP/2 (TCP) unless you opt in to the app's HTTP/3 with protocolMode: 'http3'. protocolMode: 'http3' uses QUIC only and fails when it cannot connect. The iOS process uses protocolMode: 'auto': it tries HTTP/3 like the iOS app and silently falls back to HTTP/2 when QUIC does not get through, for example behind a proxy without UDP. protocolMode: 'http2' forces HTTP/2 on either.

See which protocol was used ​

Every full response reports the protocol it came over in protocol: 'h3' (HTTP/3 over QUIC), 'h2' (HTTP/2 over TCP) or 'http/1.1'. It is on the full response (JsonResponseHttp) and on the response of a typed Instagram error, not on the plain results. RequestError carries only status, body and headers and has no protocol.

ProtocolDiagnostics returns the current state of the transport client, one entry per host it has talked to (empty before the first request).

ts
import { AndroidIgpapi, JsonResponseHttp, ProtocolDiagnostics } from '@igpapi/android';
import { Effect } from 'effect';

const username = process.env['IG_USERNAME'] ?? '';

const report = await AndroidIgpapi.install({
  seed: username,
  // Android uses HTTP/2 unless you opt in to the app's HTTP/3. This fails when QUIC cannot connect.
  protocolMode: 'http3',
  main: () =>
    Effect.gen(function* () {
      // Every full response reports the protocol it came over: 'h3', 'h2' or 'http/1.1'.
      const json = yield* JsonResponseHttp;
      const response = yield* json({ url: 'api/v1/users/123456/info/' });

      // The state of the transport, one entry per host it has talked to.
      const hosts = yield* ProtocolDiagnostics;
      return { protocol: response.protocol, hosts: hosts.map(entry => `${entry.origin}: ${entry.protocol}`) };
    }),
});
console.log(report);
FieldMeaning
originhost:port, e.g. i.instagram.com:443
protocolprotocol of the last request to that host: 'h3' or 'h2'
fallbackReasonwhy HTTP/3 was not used the last time, in the transport's words; it stays after HTTP/3 works again, so read it together with protocol
fallbackAtMswhen that happened (Unix milliseconds)
http3RetryInMshow long until HTTP/3 is tried again; until then requests go over HTTP/2 without trying
consecutiveFailuresHTTP/3 connection failures in a row; a working HTTP/3 connection resets it

A reason such as "the proxy cannot carry UDP" (an http:// proxy), or a QUIC connection that did not come up within the attempt window (a proxy or network that drops UDP), together with a growing consecutiveFailures, means HTTP/3 does not get through that client's network path. A reason saying the attempt continues in the background is the first, bounded wait: the next request may already use HTTP/3. On Android the default protocolMode is http2, which never tries HTTP/3, so fallbackReason stays empty. The diagnostics describe the running process and are not part of a checkpoint. Setting the IGPAPI_TRANSPORT_LOG environment variable also makes the transport print every fallback to stderr.

On iOS ​

Tell whether an iOS client behind a proxy is stuck on the slower channel:

ts
import { IosIgpapi, ProtocolDiagnostics } from '@igpapi/ios';
import { Effect } from 'effect';

const username = process.env['IG_USERNAME'] ?? '';

// Tell whether an iOS client behind a proxy is stuck on the slower channel.
await IosIgpapi.install({
  seed: username,
  main: () =>
    Effect.gen(function* () {
      for (const entry of yield* ProtocolDiagnostics) {
        if (entry.protocol === 'h2' && entry.fallbackReason !== undefined) {
          yield* Effect.logWarning(
            `${entry.origin}: on HTTP/2 since ${new Date(entry.fallbackAtMs ?? 0).toISOString()} — ${entry.fallbackReason};` +
              ` ${entry.consecutiveFailures} failure(s) in a row, next HTTP/3 try in ${entry.http3RetryInMs ?? 0} ms`,
          );
        }
      }
    }),
});

Run it inside the same program as the requests it should describe: a new process has a new transport client.

The pages of this site are the README files of the repository. The API reference is generated from the source.