SReverseby Simpa Labs

Retrofit extraction

How to extract Retrofit endpoints from an APK

Retrofit describes an API as Kotlin or Java interfaces, then builds each request when a method is invoked. The descriptions are readable in a decompiled APK, and the resolution rules decide what the real URL looks like.

Identify the service interfaces

Every Retrofit service is an interface, though not every interface is a service. Annotated methods are the marker that matters. A single interface often covers one backend area, so grouping the annotated interfaces by package gives you a first draft of the API surface.

The client that creates them is the Retrofit instance, built through a builder. The builder call that names the host is the anchor for everything else, and the create call that names an interface is the link between a service and the interface you just read.

Read the method annotations

Each method carries an annotation for the verb and one for the path. Record them together with the parameter annotations, because the parameter list determines how the path becomes a URL.

  • A path parameter replaces a placeholder in the template. The value is substituted verbatim or URL encoded depending on the encoded attribute.
  • A query parameter becomes a URL parameter when its value is not null. Passing null commonly omits the parameter rather than sending an empty value.
  • A query map expands a collection of key and value pairs into several parameters.
  • A field parameter contributes one entry to a form body, and the form annotation on the method decides the content type.
  • A body parameter is serialised by the converter attached to the builder. The converter choice is what tells you whether the payload is JSON, Protocol Buffers or something else.
  • A header parameter adds a request header on every call to that method.

Response types tell you how state flows

The declared return type is often more informative than the path. A call type wraps a response you must execute. A suspend function returns the body directly and moves the suspension to the coroutine machinery. A flow or observable type means the method does not map onto one request, and calling it once in a script will not reproduce what the app receives.

Wrapped response types also matter. When the body type is a response object rather than the payload, the caller inspects the status code and headers itself, and the app's error handling around that endpoint is worth reading before you reproduce it.

Reconstruct the resolved URL

A path annotation is relative, and the meaning of relative depends on two rules that trip people up.

  • The base URL and the relative path are combined with the standard URL resolution rules. A path that begins with a slash replaces the path portion of the base rather than appending to it, which changes the result in a way that surprises most readers.
  • Base URL normalisation requires a trailing slash on the host value. If the configuration omits it, a relative path can resolve against the parent directory instead.

An interface can also accept a full URL as an argument, which bypasses the base entirely and moves the host into application code. Those methods usually serve content delivery or a second service, and they need to be recovered from the call sites rather than the annotations.

Handle obfuscated and Kotlin builds

Renamed parameters are the normal case in a release build, so a decompiled method may read like a single letter followed by an unknown type. Kotlin metadata helps here: the metadata annotation on a class often preserves the original parameter names and nullability, and decompilers use it to restore readable signatures. When the metadata has been stripped, recover meaning from the parameter annotations and from how the value is used in the enclosing coroutine.

An obfuscated interface still shows its path constants. What you lose is the naming around them, which is why a naming pass that maps obfuscated identifiers to the endpoint they call makes the extraction usable by anyone other than the person who ran it.

Test the endpoint list against recorded traffic

Build the list, then compare it with what the app sends. Interceptors and generated clients add headers, and some endpoints accept parameters appended by code outside the interface, so the annotation set is a starting point rather than the final API specification. Drive each screen and match every request to an extracted method. Anything unmatched is a gap in the extraction, and anything extracted with no traffic may be a retired endpoint or one behind a flag.

Once the list matches behaviour, the annotations become an API description you can generate code from. That generation step is what the Retrofit extraction service performs, and it is the difference between a collection of recovered paths and a client you can schedule and monitor.

Reviewed 28 September 2026 · SReverse research desk

Start your full APK reconstruction

Send the full APK

Projects start at $120. Choose WhatsApp or email, then attach the APK in the app that opens. We reply within one hour with the next step and send the fixed quote after review.

Want us to contact you?