SReverseby Simpa Labs

Endpoint discovery

Reading endpoints from compiled annotations and the manifest

An APK usually contains everything needed to call its backend: paths, headers, parameters and the order the server expects them in. The work is finding that material in a compiled artifact and proving each endpoint is real.

What an endpoint looks like once the code is compiled

The path strings your code calls live in the compiled output. Java and Kotlin sources become DEX bytecode, which holds string constants, type descriptors, method signatures and annotations. A relative path such as a user profile resource is a constant in the DEX string table. The HTTP method attached to it is an annotation value recorded in the same file.

Obfuscation changes class and method names rather than the shape of a request. A renamed class still carries the string it will pass to the HTTP client. That fact makes endpoint recovery mostly a search problem, and it explains why a build protected with ProGuard or R8 still exposes its own URLs.

Read the manifest and the application class first

The manifest tells you which code runs at startup. Look for the android:name attribute on the application element, then read that class. Apps commonly initialise the networking stack there or delegate to an initialiser that a dependency injection framework registers. From that entry point you reach the HTTP client construction, and in Retrofit-based apps that construction is where the interface set is declared.

Manifest metadata also carries configuration that is not in code: API keys, environment flags and base URL overrides placed there by the build system for different product flavours.

Annotations are compile-time values, so they survive renaming

Retrofit records the HTTP method and path in annotations such as GET, POST, PUT and DELETE. The Kotlin and Java compilers emit those annotation values into the DEX, and the runtime library reads them through reflection. Because a string constant cannot be renamed without changing program behaviour, the paths remain readable in a stripped build.

Two paths find them quickly:

  • Decompile with jadx. Open the sources tree, sort by package and read the files that contain interface declarations. Search for the annotation names and for the character sequence that begins a path.
  • Skip the decompiler and search the raw DEX. The apktool disassembly or a strings pass over the classes.dex files exposes every path-shaped constant, including those inside methods that a decompiler failed to reconstruct.

Turn annotations into a candidate list

A decompiled interface gives you enough to write the call down. For each method, record the HTTP verb, the path, the parameter annotations and the declared return type.

  • A path annotation gives the resource, and the value may contain placeholders that the client substitutes at call time.
  • A query annotation maps a method argument to a URL parameter, and its encoded flag changes how the value is escaped.
  • A body annotation marks the serialised payload. The serialiser, often Gson, Moshi or a Protocol Buffers converter, decides the wire format.
  • A header annotation contributes a static header, while a header map argument accepts dynamic ones.
  • A form url encoded annotation means the body is sent as form fields, which matters when you rebuild the call elsewhere.

Several interface methods can map onto one server path because the differences sit in the parameters or the verb. Treat the annotation as the primary evidence and the method signature as a refinement.

Where the base URL is not in the annotation

Retrofit stores only the relative path. The host comes from the client construction, and it is frequently assembled from a build constant, a resource string, or a value fetched from a configuration endpoint on first launch. A candidate list without the host is not yet callable, so resolve that piece before you test anything. Some apps keep several hosts and select one per request, which you will only notice by reading the interceptor chain rather than a single builder call.

Confirm each endpoint by observing the request

A decompiled annotation tells you what the developer wrote. It does not prove the server still answers, and it says nothing about parameters that are appended outside the interface. Confirmation comes from watching a real call leave the device.

  • Attach a logging interceptor at the end of the chain, or hook the dispatcher that executes the call, so you see the final URL and body after every interceptor has run.
  • Drive the screen that triggers the call and record the request. The recorded URL is the ground truth the decompiled path has to match.
  • Compare the header set against the ones you extracted. Interceptors add headers that no interface declaration mentions.

Discrepancies between the two views are the useful signal. A path that never appears in traffic is either dead code or gated behind a feature flag. A request that carries headers you never found means part of the client configuration lives somewhere you have not read yet.

Working at the level of the whole app

Once you can list endpoints, the remaining questions are about the system around them: which calls must run in sequence, which values are produced by one call and consumed by the next, and which responses the server returns when a derived value goes stale. Answering those takes you from a set of URLs to a client that behaves like the app, and that is the deliverable the APK to API service produces.

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?