# PROTOCOL1 — Print Deploy client protocol through the Printer JWT

This document describes the HTTP protocol used by the PaperCut Print Deploy
client (observed version **1.10.3838**, Debian build) from startup through the
issuance of the **Printer JWT** — the credential later used for the printer
queue URL and IPP printing. The Printer JWT is the end of this document; the
`/printer-url`, driver download, and IPP phases are covered separately.

Everything here is derived from the mitmproxy tnetstring capture
`convo6_reallyfull.dump` (32 flows, captured 2026-11-10 07:50:39–07:51:06 UTC,
server `appServerEnvVersion` 15). Secrets in this document are redacted or
shown only as shape/length; no live token is reproduced.

---

## 1. Topology and constants

| Thing | Value in the capture |
|---|---|
| Print Deploy server (API) | `https://printdeploy.asg.nl:9174` |
| TCP peer actually connected to | `172.25.1.14:9174` (Host header still `printdeploy.asg.nl:9174`) |
| Mobility Print server | `https://172.25.1.14:9164` (taken from the printer's `connection.name`) |
| OAuth loopback callback | `http://localhost:9176/oauth2/microsoft/callback` |
| Printer queue (out of scope) | `http://172.25.1.14:9163/printers/...` |
| Local CUPS/proxy port seen later | `127.0.0.1:9177` (inside IPP `printer-uri` attributes) |
| Client User-Agent | `Print Deploy Client/1.10.3838/Debian` |
| Transport | HTTP/1.1 over TLS, `Accept-Encoding: gzip` |

The client selects the Mobility Print base URL from the printer object returned
by `/deploy/printers` (`connection.name`), not from `/deploy/config`.

### Request conventions

* JSON request bodies are sent **without a `Content-Type` header**. The bodies
  are compact JSON with no insignificant whitespace.
* The client sends `Host`, `User-Agent`, `Content-Length` (for bodies) and
  `Accept-Encoding: gzip`. It sends no `Accept`, no cookies, and no custom
  tracing headers on these calls.
* Responses are `application/json; charset=utf-8` unless noted.
* `Authorization` is either `Bearer <PaperCut session token>` (Print Deploy
  API) or `Basic base64(<upn>:<Microsoft access token>)` (Mobility Print
  `/token`).
* The captured client treats HTTP 404 on the branding asset and the legacy
  `/printer-urls` probe as normal, non-fatal outcomes.

---

## 2. End-to-end sequence

```
Client                                   Print Deploy (9174)              Microsoft / Mobility (9164)
  |                                            |
  |-- GET  /deploy/config -------------------->|
  |<-- 200 config JSON ------------------------|
  |-- GET  /brands/auth-dialog-logo.png ------>|
  |<-- 404 (tolerated) ------------------------|
  |-- GET  /i18n/en-US ----------------------->|
  |<-- 200 translations -----------------------|
  |-- POST /deploy/oauth/url {provider,state} >|
  |<-- 200 {authURL} --------------------------|
  |                                            |
  |   Browser: GET authURL -> login.microsoftonline.com -> user signs in
  |   Microsoft redirects browser to http://localhost:9176/oauth2/microsoft/callback?code=...&state=...
  |                                            |
  |-- POST /deploy/oauth/user {provider,code} >|-- redeem code (server side) --> Microsoft
  |<-- 200 {username,sessionToken,token{...}} -|
  |-- GET  /deploy/config -------------------->|
  |<-- 200 config JSON ------------------------|
  |-- POST /deploy/printers (Bearer session) ->|
  |<-- 200 {printers[...]} --------------------|
  |-- GET  /printer-urls (Mobility) ---------------------------------------->|
  |<-- 404 (tolerated) ------------------------------------------------------|
  |-- POST /deploy/oauth/token (Bearer session, {}) >|
  |<-- 200 {username,sessionToken,token{...}} -|
  |-- GET  /printer-urls (Mobility) ---------------------------------------->|
  |<-- 404 (tolerated) ------------------------------------------------------|
  |-- GET  /i18n/en-US ----------------------->|
  |<-- 200 translations -----------------------|
  |-- GET  /token?printerName=... (Basic upn:access_token) ---------------->|
  |<-- 200 {"token":"<Printer JWT>"} ---------------------------------------|
  |                                            |
  |   Printer JWT used for /printer-url and IPP (next document)
```

---

## 3. Credential and state inventory

| Item | Where it comes from | Shape in the capture | Used as |
|---|---|---|---|
| OAuth `state` | generated by the client | 32 alphanumeric chars (random, e.g. `<32-char state>`) | CSRF check on the callback; sent to `/deploy/oauth/url` |
| Authorization code | browser callback `?code=` | ~1436 chars, `1.ASAA…` | POSTed once to `/deploy/oauth/user` |
| PaperCut session token | `/deploy/oauth/user` → `sessionToken` | 8156 chars, opaque | `Authorization: Bearer` on `/deploy/printers` and `/deploy/oauth/token` |
| Microsoft access token | `/deploy/oauth/user` → `token.access_token` | JWT, 2731 chars (RS256), Graph audience | HTTP Basic password on `/token` |
| Microsoft refresh token | → `token.refresh_token` | opaque, 1541 chars, `1.ASAA…` | returned to the client; refresh is performed server-side |
| Printer JWT | `/token` → `token` | JWT, 141 chars (HS256) | downstream `/printer-url` and IPP `Authorization: Bearer` |

The client keeps all four tokens. The PaperCut session token, not the
Microsoft token, authenticates the Print Deploy API.

---

## 4. Step-by-step

Timestamps are UTC from the capture.

### 4.1 `GET /deploy/config` — discovery

```
GET /deploy/config HTTP/1.1
Host: printdeploy.asg.nl:9174
User-Agent: Print Deploy Client/1.10.3838/Debian
Accept-Encoding: gzip
```

`200`, `application/json`, 1053 bytes. Fields used by the client:

```json
{
  "accessibleHost": "printdeploy.asg.nl",
  "AccessibleMobilityPrintTLSPort": 9164,
  "accessiblePrintDeployTLSPort": 9174,
  "appServerEnvVersion": 15,
  "AuthMethods": ["username", "microsoft"],
  "pollingConfig": {"intervalSecs": 900, "minRandomOffsetSecs": -300, "maxRandomOffsetSecs": 300},
  "sharedDeviceEnabled": true,
  "userIdentityMethod": "PROMPT",
  "serverlessConfig": {
    "applicationServer": "papercut.asg.nl",
    "applicationServerPort": 9191,
    "serverlessPrintingMode": false,
    "directPrintingMode": true,
    "tls": false,
    "tlsVerifyCertificate": false,
    "...": "..."
  },
  "vdiDeploymentIntervalSecs": 10,
  "setDefaultPrinterEnabled": true
}
```

The full object also carries `selfServiceHelpLink`, `userNotificationsEnabled`,
`useStandardDriverPath`, `detectNetworkChange`, and serverless printing knobs.
`AuthMethods` advertises the sign-in methods; the capture uses `microsoft`.
The 900 s ±300 s polling interval is the client's printer-refresh cadence; the
capture is too short to observe a poll or its relationship to token refresh.

### 4.2 `GET /brands/auth-dialog-logo.png` — optional branding

Same headers as 4.1.

`404`, `text/plain`, 19 bytes: `404 page not found`. The client tolerates the
missing logo (a `200` would be used in the sign-in dialog).

### 4.3 `GET /i18n/en-US` — translations

Same headers. `200`, `Content-Length: 4097` (4093 decoded bytes),
`application/json; charset=utf-8`, 73 keys,
`access-control-allow-origin: *`. Example keys: `AddPrintersTitle`,
`Popup-title`, `SearchPlaceholder`, `StatusNotInstalled`.

### 4.4 `POST /deploy/oauth/url` — begin sign-in

```
POST /deploy/oauth/url HTTP/1.1
Host: printdeploy.asg.nl:9174
User-Agent: Print Deploy Client/1.10.3838/Debian
Content-Length: 69
Accept-Encoding: gzip

{"providerId":"microsoft","state":"<32-char client state>"}
```

`200` (`application/json; charset=utf-8`, 353 bytes):

```json
{"authURL":"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?..."}
```

The observed `authURL` query parameters:

| Parameter | Value |
|---|---|
| `client_id` | `9ff4268d-5401-4353-97c9-5980ee58e068` |
| `redirect_uri` | `http://localhost:9176/oauth2/microsoft/callback` |
| `response_type` | `code` |
| `scope` | `openid offline_access user.read profile email` |
| `access_type` | `offline` |
| `state` | the same 32-char state sent in the POST body |

`offline_access` is requested, which is why a refresh token is issued. No PKCE
(`code_challenge`) parameters were observed. The `state` echoed by the server
must match the client-generated state.

### 4.5 Browser sign-in and loopback callback

The client opens `authURL` in the system browser and must listen on
**`localhost:9176`** (the redirect URI; depending on resolution that can be
IPv4 or IPv6) for:

```
GET /oauth2/microsoft/callback?code=<authorization code>&state=<state>
```

The client checks `state` against its own value. The authorization code is
single-use and short-lived. In the capture the code was ~1436 characters,
starting `1.ASAA…`. Any `error`/`error_description` query parameters abort the
flow. (No client-side token endpoint call to Microsoft occurs; the PaperCut
server redeems the code.)

### 4.6 `POST /deploy/oauth/user` — exchange code for a session

```
POST /deploy/oauth/user HTTP/1.1
Host: printdeploy.asg.nl:9174
User-Agent: Print Deploy Client/1.10.3838/Debian
Content-Length: 1472
Accept-Encoding: gzip

{"providerId":"microsoft","code":"<authorization code>"}
```

`200`, `application/json; charset=utf-8`, **chunked**, 12655 bytes:

```json
{
  "username": "<upn>",
  "email": "<upn>",
  "providerId": "microsoft",
  "sessionToken": "<8156-char opaque PaperCut token>",
  "token": {
    "access_token": "<Microsoft Graph JWT, 2731 chars>",
    "token_type": "Bearer",
    "refresh_token": "<1541-char Azure AD refresh token>",
    "expiry": "2026-11-10T10:04:42.7750377+01:00",
    "expires_in": 4437
  }
}
```

Notes:

* The server returns the Microsoft refresh token to the client. In the
  observed client, later refresh calls go through the PaperCut server, not
  directly to Microsoft.
* The access token JWT claims: `iat`/`nbf` 2026-11-10 07:45:51Z,
  `exp` 2026-11-10 09:04:49Z, `aud`
  `00000003-0000-0000-c000-000000000000` (Microsoft Graph),
  `scp` `email openid profile User.Read`. At receipt (07:50:51Z) it had
  **4437 s (~74 min)** left. `iat` is ~5 min before receipt, so use the
  `exp`/`expires_in` values as authoritative.
* The session token is the durable handle for the Print Deploy API; see §5.1.

### 4.7 `GET /deploy/config` — after sign-in

Identical to 4.1 and repeated by the client after authentication, at
07:50:45Z.

### 4.8 `POST /deploy/printers` — printer list and driver metadata

```
POST /deploy/printers HTTP/1.1
Host: printdeploy.asg.nl:9174
User-Agent: Print Deploy Client/1.10.3838/Debian
Content-Length: 272
Authorization: Bearer <PaperCut session token>
printdeployauthenticationtype: microsoft
Accept-Encoding: gzip

{"version":"1.10.3838","printingSystem":"CUPS",
 "loggedInUser":{"username":"<user>","type":"LocalMachine"},
 "machine":{"hostname":"<host>",
   "os":{"name":"darwin","osName":"Debian","version":"12","arch":"amd64"},
   "ipAddresses":["<ip>"],"activeDirectoryDomainName":""}}
```

`200`, `application/json; charset=utf-8`, 783 bytes. The machine fingerprint
selects the driver platform (`os.name`/`os.version`/`os.arch`). The response:

```json
{
  "printers": [{
    "name": "Follow-Me",
    "description": "Follow-Me",
    "driver": {
      "os": "darwin",
      "name": "Follow-Me",
      "zip": {"name": "/deploy/drivers/mac/26.6-arm64/Follow-Me.zip",
              "sha256": "<hex>"},
      "capabilities": {"name": "/deploy/capabilities/mac/26.6-arm64/Follow-Me.cap",
                       "sha256": "<hex>"},
      "defaults": {"name": "", "sha256": ""},
      "file": "Follow-Me.ppd",
      "extras": null
    },
    "connection": {
      "type": "mobilityprint",
      "name": "https://172.25.1.14:9164",
      "host": "", "protocol": "", "number": 0, "queue": "",
      "snmpCommunity": "", "snmpDeviceIndex": 0, "doubleSpool": false
    },
    "defaults": {"color": "", "plex": "", "resolution": 0},
    "optional": false,
    "zoneName": "ASG",
    "defaultprinter": false,
    "advancedPrintingFeatures": false
  }]
}
```

`connection.name` is the Mobility Print base URL used for the rest of the
flow. The driver `sha256` values are advisory in the captured client's
behaviour (see §7).

### 4.9 `GET /printer-urls` — legacy probe (Mobility Print)

```
GET /printer-urls HTTP/1.1
Host: 172.25.1.14:9164
User-Agent: Print Deploy Client/1.10.3838/Debian
Accept-Encoding: gzip
```

`404`, `text/plain`, 19 bytes `404 page not found`. Expected; the client falls
back to per-printer URLs.

### 4.10 `POST /deploy/oauth/token` — server-side token refresh

```
POST /deploy/oauth/token HTTP/1.1
Host: printdeploy.asg.nl:9174
User-Agent: Print Deploy Client/1.10.3838/Debian
Content-Length: 2
Authorization: Bearer <PaperCut session token>
Accept-Encoding: gzip

{}
```

`200`, chunked, 12655 bytes; same schema as 4.6. **Observed no-op:** in the
capture this returned byte-identical `sessionToken`, `access_token`,
`refresh_token`, `expiry` and `expires_in` (4437 s) as 4.6. The call was made
while the access token still had ~74 min left, so the server evidently
short-circuits when the token is still valid. The capture therefore does not
exercise a real Microsoft refresh; a real refresh is expected once the access
token is near/after expiry. The client repeats this call periodically.

### 4.11 `GET /printer-urls` — repeated

Identical to 4.9, repeated after the token refresh. `404` again.

### 4.12 `GET /i18n/en-US` — repeated

Identical to 4.3. `200`, `Content-Length: 4097` (4093 decoded bytes), 73 keys.

### 4.13 `GET /token` — mint the Printer JWT

This is the boundary of this document. It is a Mobility Print call, **not** a
Print Deploy API call, and it authenticates with the *Microsoft access token*,
not the PaperCut session token:

```
GET /token?printerName=Follow-Me HTTP/1.1
Host: 172.25.1.14:9164
User-Agent: Print Deploy Client/1.10.3838/Debian
Authentication-Type: openid/microsoft
Authorization: Basic base64(<upn>:<Microsoft access token>)
Accept-Encoding: gzip
```

The Basic username is the signed-in UPN; the password is the **Microsoft
Graph access token** (the 2731-char JWT from §4.6/4.10), concatenated with a
colon. Example construction:

```
plaintext = upn + ":" + access_token
header    = "Basic " + base64(plaintext)
```

Response: `200`, `text/plain; charset=utf-8` (JSON despite the content type),
153 bytes, `Server: Mobility Print`:

```json
{"token":"<Printer JWT>"}
```

The Printer JWT is 141 characters, HS256. Its payload contains only:

```json
{"exp":1825833046,"username":"<upn>"}
```

There is **no `iat`, `aud`, `iss`, `jti`, or printer/queue claim**. In the
capture `exp` is 2027-11-10 07:50:46Z — exactly 365 days after issuance. The
signature key is the Mobility Print server's secret. This token is what the
client uses for `/printer-url` and for IPP `Authorization: Bearer` requests
(documented separately).

---

## 5. Token formats in detail

### 5.1 PaperCut session token

* Length 8156 characters, URL-safe base64 alphabet including `=` padding
  (`A–Z a–z 0–9 - _ =`).
* Base64url-decoding yields:

```
<issue_epoch>|<payload>|<hmac>
1794297045    <6072-char base64url>    <32 bytes>
```

* `issue_epoch` is the Unix issue time (observed 2026-11-10 07:50:45Z).
* `payload` decodes to 4554 bytes of high-entropy data (entropy ≈ 7.96
  bits/byte; no readable JSON or claims), consistent with authenticated
  encryption. Its plaintext and expiry are not recoverable without the server
  key.
* The final 32 bytes are an HMAC over the preceding fields.
* The same value was returned unchanged by the observed refresh call.

### 5.2 Microsoft access token

Standard JWT (RS256), Graph audience, scopes
`email openid profile User.Read`. Observed lifetime: `exp - receipt` = 4437 s
(~74 min); `iat` ~5 min earlier than receipt. Treated as a bearer credential
only as the HTTP Basic password for `/token`.

### 5.3 Microsoft refresh token

Opaque Azure AD refresh token (1541 chars, `1.ASAA…`), returned to the client
in the `token` object. No expiry is present in the response; lifetime is
governed by Azure AD policy (public-client refresh tokens are commonly valid
for ~90 days sliding, shortened by tenant policy/revocation). In the observed
flow, renewal is delegated to the PaperCut server via `/deploy/oauth/token`
using the session token.

### 5.4 Printer JWT

* 141 chars, three dot-separated parts, HS256.
* Payload: `{"exp":<epoch>,"username":"<upn>"}` — nothing else.
* Lifetime: exactly 365 days from `/token` issuance in the capture.
* Not bound to a printer or session; it is a user-level printing credential
  for the Mobility Print instance that signed it.
* Revocation, server key rotation, and account/printer state are not visible
  in the token and cannot be inferred from the capture.

---

## 6. Lifetimes and refresh order

Measured in the capture:

| Credential | Expires | Window |
|---|---|---:|
| Microsoft access token | 2026-11-10 09:04:49Z | **~74 min** from receipt |
| PaperCut session token | not visible (opaque/encrypted payload) | unknown |
| Microsoft refresh token | not in response | Azure AD policy |
| Printer JWT | 2027-11-10 07:50:46Z | **365 days** |

Order of expiry and implications:

1. **Microsoft access token first (~74 min).** It is needed only to mint a new
   Printer JWT via `/token`. It is not needed for the Print Deploy API calls.
2. **Session token next.** It authenticates `/deploy/printers` and
   `/deploy/oauth/token`; its expiry is the gate on refreshing the access token
   without a browser. Unknown from the capture.
3. **Microsoft refresh token** enables the server to renew access tokens
   silently; tenant policy determines its life.
4. **Printer JWT last (365 days).** Printing via IPP needs only this token, so
   a cached Printer JWT permits printing for up to a year without any OAuth
   refresh, subject to revocation/key rotation.

With refresh working, access tokens can be renewed via `/deploy/oauth/token`
and new Printer JWTs minted at will; continuous printing is then bounded by
the session/refresh-token chain rather than by the 365-day JWT.

---

## 7. Observed quirks and error behaviour

* JSON POST bodies are sent **without `Content-Type`**; the server still parses
  them. Do not assume a content-type requirement.
* The `Host` header is `printdeploy.asg.nl:9174` even though the TCP peer in
  the capture is `172.25.1.14:9174`; preserve the virtual host name.
* `/deploy/oauth/user` and `/deploy/oauth/token` responses are **chunked**.
* `GET /token` returns JSON with `Content-Type: text/plain; charset=utf-8`.
* The `/deploy/oauth/token` refresh is a no-op while the access token is still
  valid (identical tokens returned); the first meaningful refresh must be
  tested after expiry.
* The driver `sha256` advertised in `/deploy/printers` and in the driver
  `catalog.json` does not appear to have been enforced in the captured run:
  the captured driver zip body hashes to `41cacc2b…` while the server
  advertised `d1f63ebc…`, and the client continued. Treat the hash as advisory
  unless implementing strict verification.
* 404 on `/brands/auth-dialog-logo.png` and `/printer-urls` is normal and must
  not abort the flow.
* HTTP 401/403 from `/deploy/printers`, `/deploy/oauth/token`, or `/token`
  indicates an expired/invalid session, access token, or wrong
  `Authentication-Type`.
* The client does not send cookies; there is no cookie-based session.

---

## 8. Implementation notes

Minimal state machine:

```
state = random 32 chars
config = GET /deploy/config
GET /brands/auth-dialog-logo.png        # tolerate 404
GET /i18n/en-US
auth_url = POST /deploy/oauth/url {providerId, state}
code = wait_for_callback(localhost:9176, state)
session, ms_tokens = POST /deploy/oauth/user {providerId, code}
GET /deploy/config
printers = POST /deploy/printers (Bearer session, machine fingerprint)
GET {mobility}/printer-urls              # tolerate 404
ms_tokens = POST /deploy/oauth/token (Bearer session, {})
GET {mobility}/printer-urls              # tolerate 404
GET /i18n/en-US
printer_jwt = GET {mobility}/token?printerName=X
             (Basic base64(upn + ":" + ms_tokens.access_token),
              Authentication-Type: openid/microsoft)
```

Implementation details that matter:

* Generate a fresh `state` and verify it on the callback.
* Bind the loopback listener on port 9176 (the redirect URI is fixed by the
  server's OAuth app). Handle both IPv6 and IPv4 localhost, or bind
  dual-stack.
* Use the `exp`/`expires_in` from the response rather than deriving expiry from
  `iat`.
* Preserve the session token across calls; it is large (8 KB) and opaque.
* For `/token`, the Basic password is the Microsoft access token, not the
  PaperCut session token.
* Send the `printdeployauthenticationtype: microsoft` header on
  `/deploy/printers`; it is not sent on `/deploy/oauth/token`.

Reproduction tooling in this repository:

* `extract_tokens.py` — dumps all credential values from the capture.
* `printdeploy_client.py` — interactive step-by-step implementation of
  §4.1–4.13 (plus printer URL and driver downloads).
* `acquire_queue_info.py` — minimal implementation of §4.4–4.13 for a CUPS
  relay; writes the Printer JWT and queue URL to YAML.
* `print_pdf_jwt.py` — downstream consumer of the Printer JWT for PDF/IPP.

---

## 9. Security notes

* All of the credentials above are bearer credentials. Possession is
  sufficient; no client certificate or device binding is used.
* The Printer JWT is user-level, valid for a year in the capture, and carries
  no printer/audience/session binding. It should be treated like a password
  for the Mobility Print instance.
* The session token's payload is high-entropy and appears to be encrypted and
  authenticated, but it is still a bearer token for the Print Deploy API.
* The OAuth flow uses a fixed loopback redirect with no PKCE observed; the
  `state` parameter is the CSRF protection. Any local process that can bind or
  race port 9176 could interfere with the callback.
* The TLS verification posture of the real client is not provable from these
  flows alone. The capture contains a PaperCut-issued certificate on 9174; the
  tooling defaults to no verification and offers `--verify`/`--cacert`.
* Do not commit captured tokens. Treat the capture itself as sensitive: it
  contains live-looking Microsoft and PaperCut credentials.

---

## Appendix A — Endpoint summary (through the Printer JWT)

| # | Method | URL | Auth | Success | Notes |
|---|---|---|---|---|---|
| 1 | GET | `{pd}/deploy/config` | none | 200 | discovery |
| 2 | GET | `{pd}/brands/auth-dialog-logo.png` | none | 200/404 | 404 tolerated |
| 3 | GET | `{pd}/i18n/en-US` | none | 200 | translations |
| 4 | POST | `{pd}/deploy/oauth/url` | none | 200 | `{providerId,state}` → `{authURL}` |
| 5 | — | browser → `localhost:9176/oauth2/microsoft/callback` | — | code+state | local listener |
| 6 | POST | `{pd}/deploy/oauth/user` | none | 200 | `{providerId,code}` → session + MS tokens |
| 7 | GET | `{pd}/deploy/config` | none | 200 | repeated |
| 8 | POST | `{pd}/deploy/printers` | Bearer session | 200 | machine fingerprint → printers |
| 9 | GET | `{mp}/printer-urls` | none | 404 | legacy probe, tolerated |
| 10 | POST | `{pd}/deploy/oauth/token` | Bearer session | 200 | `{}` → refreshed token object |
| 11 | GET | `{mp}/printer-urls` | none | 404 | repeated |
| 12 | GET | `{pd}/i18n/en-US` | none | 200 | repeated |
| 13 | GET | `{mp}/token?printerName=…` | Basic upn:access_token + `Authentication-Type: openid/microsoft` | 200 | **Printer JWT** |

`{pd}` = `https://printdeploy.asg.nl:9174`, `{mp}` = `https://172.25.1.14:9164`
(as returned in `connection.name`).

## Appendix B — Capture flow map

| Flow | Request | Status | Body size |
|---:|---|---:|---:|
| 1 | `GET /deploy/config` | 200 | 1053 |
| 2 | `GET /brands/auth-dialog-logo.png` | 404 | 19 |
| 3 | `GET /i18n/en-US` | 200 | 4097 |
| 4 | `POST /deploy/oauth/url` | 200 | 353 |
| 5 | `POST /deploy/oauth/user` | 200 | 12655 (chunked) |
| 6 | `GET /deploy/config` | 200 | 1053 |
| 7 | `POST /deploy/printers` | 200 | 783 |
| 8 | `GET /printer-urls` (9164) | 404 | 19 |
| 9 | `POST /deploy/oauth/token` | 200 | 12655 (chunked) |
| 10 | `GET /printer-urls` (9164) | 404 | 19 |
| 11 | `GET /i18n/en-US` | 200 | 4097 |
| 12 | `GET /token?printerName=Follow-Me` | 200 | 153 |
| 13 | `GET /printer-url?...` | 200 | 129 (next document) |

Flows are the client-side HTTP transactions in `convo6_reallyfull.dump`;
flows 14–32 (driver downloads and IPP) are outside this document's scope.
