From 904e950326a79617c2c640fabcf187af67a4a2d8 Mon Sep 17 00:00:00 2001 From: jonathanpopham Date: Sat, 24 Jan 2026 12:45:05 -0500 Subject: [PATCH 1/4] docs: add async polling guide and update examples for SDK 0.6.0 - Add new async-polling.mdx page explaining the job-based API model - Update quickstart to show 202 responses and polling pattern - Update authentication to explain idempotency key's role in polling - Update concepts to note all graph generation is async - Add SDK SupermodelClient usage examples with polling configuration Closes supermodeltools/supermodel-public-api#336 --- async-polling.mdx | 172 +++++++++++++++++++++++++++++++++++++++++++++ authentication.mdx | 7 +- concepts.mdx | 2 + docs.json | 1 + quickstart.mdx | 82 ++++++++++++++------- 5 files changed, 236 insertions(+), 28 deletions(-) create mode 100644 async-polling.mdx diff --git a/async-polling.mdx b/async-polling.mdx new file mode 100644 index 0000000..49c965b --- /dev/null +++ b/async-polling.mdx @@ -0,0 +1,172 @@ +--- +title: 'Async Polling' +description: 'How graph generation jobs work and how to poll for results' +icon: 'arrows-rotate' +--- + +All graph generation endpoints are **asynchronous**. When you submit a request, the API creates a background job and returns immediately with a job status. You then poll for results by re-submitting the same request with the same `Idempotency-Key`. + +## How It Works + +```mermaid +sequenceDiagram + participant Client + participant API + + Client->>API: POST /v1/graphs/dependency (file + Idempotency-Key) + API-->>Client: 202 Accepted {status: "processing", retryAfter: 5} + Note over Client: Wait retryAfter seconds + Client->>API: POST /v1/graphs/dependency (same file + same key) + API-->>Client: 202 Accepted {status: "processing", retryAfter: 5} + Note over Client: Wait retryAfter seconds + Client->>API: POST /v1/graphs/dependency (same file + same key) + API-->>Client: 200 OK {status: "completed", result: {graph: {...}}} +``` + +## Job Statuses + +| Status | HTTP Code | Description | +|--------|-----------|-------------| +| `pending` | 202 | Job is queued, waiting to be processed | +| `processing` | 202 | Job is actively being analyzed | +| `completed` | 200 | Job finished successfully, `result` field contains the graph | +| `failed` | 200 | Job encountered an error, `error` field contains the message | + +## Response Envelope + +All graph endpoints return a consistent envelope: + +```json +{ + "status": "pending | processing | completed | failed", + "jobId": "unique-job-identifier", + "retryAfter": 5, + "result": { ... }, + "error": "error message if failed" +} +``` + +- **`status`** - Current job state +- **`jobId`** - Unique identifier for the job +- **`retryAfter`** - Recommended seconds to wait before the next poll (only present for pending/processing) +- **`result`** - The graph data (only present when completed) +- **`error`** - Error description (only present when failed) + +## Polling with cURL + +Store the `Idempotency-Key` in a variable and re-use it for polling: + +```bash +IDEMPOTENCY_KEY=$(uuidgen) + +# Submit the job +curl --request POST \ + --url https://api.supermodeltools.com/v1/graphs/dependency \ + --header "Idempotency-Key: $IDEMPOTENCY_KEY" \ + --header 'X-Api-Key: ' \ + --header 'Content-Type: multipart/form-data' \ + --form file='@repo.zip' + +# Poll until completed (re-submit the same request) +curl --request POST \ + --url https://api.supermodeltools.com/v1/graphs/dependency \ + --header "Idempotency-Key: $IDEMPOTENCY_KEY" \ + --header 'X-Api-Key: ' \ + --header 'Content-Type: multipart/form-data' \ + --form file='@repo.zip' +``` + + + The server uses the `Idempotency-Key` to identify your existing job. Re-submitting the file does not create a duplicate job. + + +## Using the SDK + +The `@supermodeltools/sdk` package handles polling automatically. Install it with: + +```bash +npm install @supermodeltools/sdk +``` + +### Basic Usage + +```typescript +import { Configuration, DefaultApi, SupermodelClient } from '@supermodeltools/sdk'; +import * as fs from 'fs'; + +const config = new Configuration({ + basePath: 'https://api.supermodeltools.com', + apiKey: 'smsk_live_...', +}); + +const api = new DefaultApi(config); +const client = new SupermodelClient(api); + +// Read your zip file +const zipBuffer = fs.readFileSync('repo.zip'); +const file = new Blob([zipBuffer], { type: 'application/zip' }); + +// This handles polling internally and returns the completed result +const result = await client.generateDependencyGraph(file); +console.log(result.graph.nodes); +``` + +### Configuring Polling Behavior + +```typescript +const client = new SupermodelClient(api, { + timeoutMs: 600000, // Max wait time (default: 5 minutes) + defaultRetryIntervalMs: 3000, // Poll interval if server doesn't specify (default: 5s) + maxPollingAttempts: 120, // Max number of polls (default: 60) + onPollingProgress: (progress) => { + console.log(`Attempt ${progress.attempt}/${progress.maxAttempts} - ${progress.status}`); + }, + signal: controller.signal, // AbortSignal for cancellation +}); +``` + +### Available Methods + +| Method | Endpoint | +|--------|----------| +| `client.generateDependencyGraph(file)` | `/v1/graphs/dependency` | +| `client.generateCallGraph(file)` | `/v1/graphs/call` | +| `client.generateDomainGraph(file)` | `/v1/graphs/domain` | +| `client.generateParseGraph(file)` | `/v1/graphs/parse` | +| `client.generateSupermodelGraph(file)` | `/v1/graphs/supermodel` | + +## Error Handling + +If a job fails, the response will have `status: "failed"` with an `error` message: + +```json +{ + "status": "failed", + "jobId": "550e8400-e29b-41d4-a716-446655440000", + "error": "Nested archives are not supported" +} +``` + +Common failure reasons: + +| Error | Resolution | +|-------|------------| +| Nested archives | Exclude `.zip`/`.tar` files from your archive using `.gitattributes` with `export-ignore` | +| File exceeds size limits | Exclude large binary files from the archive | +| Blob expired | Job waited too long in the queue; retry with a new idempotency key | + + + Jobs have a limited processing window. If a job stays in `pending` status too long, the uploaded file may expire and the job will be marked as `failed`. + + +## Idempotency Key Behavior + +The `Idempotency-Key` scopes a job to your API key. Key behaviors: + +- **Same key, same user**: Returns the existing job (no duplicate processing) +- **Same key, different user**: Creates independent jobs (no conflict) +- **New key, same file**: Creates a new job (useful for re-analysis after code changes) + + + Completed jobs are retained for 24 hours. After that, submitting the same idempotency key will create a new job. + diff --git a/authentication.mdx b/authentication.mdx index 1bff73e..b380940 100644 --- a/authentication.mdx +++ b/authentication.mdx @@ -20,10 +20,11 @@ X-Api-Key: smsk_live_... ## Idempotency Key -All API requests require an `Idempotency-Key` header. This is a unique value (such as a UUID) that you generate for each request. It serves two purposes: +All API requests require an `Idempotency-Key` header. This is a unique value (such as a UUID) that you generate for each request. It serves three purposes: -1. **Safe retries**: If a request fails due to a network issue, you can safely retry it with the same idempotency key without risking duplicate operations. -2. **Request tracing**: The key is echoed back in the `X-Request-Id` response header for debugging and support purposes. +1. **Job identity**: The key identifies your graph generation job. Re-submitting the same key returns the existing job status rather than creating a duplicate. +2. **Polling**: You poll for results by re-submitting the same request with the same idempotency key. See [Async Polling](/async-polling) for details. +3. **Request tracing**: The key is echoed back in the `X-Request-Id` response header for debugging and support purposes. ```bash Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 diff --git a/concepts.mdx b/concepts.mdx index b083d9b..dc7b74e 100644 --- a/concepts.mdx +++ b/concepts.mdx @@ -6,6 +6,8 @@ icon: 'layer-group' Supermodel provides different "lenses" to view your codebase through. Each API endpoint generates a specific type of graph suited for different analysis tasks. +All graph generation is **asynchronous** — the API accepts your request, processes it in the background, and you poll for results. See [Async Polling](/async-polling) for details. + ## Dependency Graph **Endpoint:** `/v1/graphs/dependency` diff --git a/docs.json b/docs.json index 0f3ef09..8fc3943 100644 --- a/docs.json +++ b/docs.json @@ -30,6 +30,7 @@ "index", "quickstart", "authentication", + "async-polling", "concepts" ] } diff --git a/quickstart.mdx b/quickstart.mdx index 0c9aa0c..94722b4 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -21,53 +21,85 @@ The Supermodel API accepts code as a zipped archive. Navigate to your project fo zip -r repo.zip . -x ".*" -x "**/.*" -x "node_modules/*" ``` -## Step 2: Generate a graph +## Step 2: Submit a graph generation job -Use the `dependency` endpoint to generate a graph of file-level dependencies. Replace `` with your actual key. The `Idempotency-Key` should be a unique value (like a UUID) for each request. +Use the `dependency` endpoint to submit a graph generation job. Replace `` with your actual key. The `Idempotency-Key` should be a unique value (like a UUID) for each request. ```bash +IDEMPOTENCY_KEY=$(uuidgen) + curl --request POST \ --url https://api.supermodeltools.com/v1/graphs/dependency \ - --header "Idempotency-Key: $(uuidgen)" \ + --header "Idempotency-Key: $IDEMPOTENCY_KEY" \ --header 'X-Api-Key: ' \ --header 'Content-Type: multipart/form-data' \ --form file='@repo.zip' ``` +The API returns an **HTTP 202 Accepted** response with the job status: + +```json +{ + "status": "processing", + "jobId": "550e8400-e29b-41d4-a716-446655440000", + "retryAfter": 5 +} +``` + The `$(uuidgen)` command generates a unique ID automatically on macOS and Linux. On Windows, you can use `[guid]::NewGuid()` in PowerShell. -## Step 3: Interpret the response +## Step 3: Poll for results -You will receive a JSON response containing the graph nodes (files) and relationships (imports). +Graph generation is asynchronous. Poll by re-submitting the same request with the **same Idempotency-Key** until the job completes: + +```bash +# Re-submit the same request to check job status +curl --request POST \ + --url https://api.supermodeltools.com/v1/graphs/dependency \ + --header "Idempotency-Key: $IDEMPOTENCY_KEY" \ + --header 'X-Api-Key: ' \ + --header 'Content-Type: multipart/form-data' \ + --form file='@repo.zip' +``` + +When the job completes, you receive an **HTTP 200** response with the graph inside the `result` field: ```json { - "graph": { - "nodes": [ - { - "id": "src/main.ts", - "labels": ["File"], - "properties": { "name": "main.ts" } - }, - { - "id": "src/utils.ts", - "labels": ["File"], - "properties": { "name": "utils.ts" } - } - ], - "relationships": [ - { - "type": "imports", - "startNode": "src/main.ts", - "endNode": "src/utils.ts" - } - ] + "status": "completed", + "jobId": "550e8400-e29b-41d4-a716-446655440000", + "result": { + "graph": { + "nodes": [ + { + "id": "src/main.ts", + "labels": ["File"], + "properties": { "name": "main.ts" } + }, + { + "id": "src/utils.ts", + "labels": ["File"], + "properties": { "name": "utils.ts" } + } + ], + "relationships": [ + { + "type": "imports", + "startNode": "src/main.ts", + "endNode": "src/utils.ts" + } + ] + } } } ``` + + **Using the SDK?** The `@supermodeltools/sdk` package handles polling automatically. See the [Async Polling](/async-polling) guide for details. + + ## Next Steps Explore other graph types to get different insights into your code: From 435eaff2daf94130cb9f904c1d5cbc957f5e082e Mon Sep 17 00:00:00 2001 From: jonathanpopham Date: Sat, 24 Jan 2026 12:49:41 -0500 Subject: [PATCH 2/4] fix: correct timeout comment and add AbortController to example - Fix comment: 600000ms is 10 minutes, not 5 - Define AbortController before referencing controller.signal --- async-polling.mdx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/async-polling.mdx b/async-polling.mdx index 49c965b..b643808 100644 --- a/async-polling.mdx +++ b/async-polling.mdx @@ -114,8 +114,10 @@ console.log(result.graph.nodes); ### Configuring Polling Behavior ```typescript +const controller = new AbortController(); + const client = new SupermodelClient(api, { - timeoutMs: 600000, // Max wait time (default: 5 minutes) + timeoutMs: 600000, // Max wait time: 10 minutes (default: 5 minutes) defaultRetryIntervalMs: 3000, // Poll interval if server doesn't specify (default: 5s) maxPollingAttempts: 120, // Max number of polls (default: 60) onPollingProgress: (progress) => { @@ -123,6 +125,9 @@ const client = new SupermodelClient(api, { }, signal: controller.signal, // AbortSignal for cancellation }); + +// To cancel polling at any point: +// controller.abort(); ``` ### Available Methods From 9d9257c584890884e1d7d782a49a826ee28d7d18 Mon Sep 17 00:00:00 2001 From: jonathanpopham Date: Sat, 24 Jan 2026 13:14:13 -0500 Subject: [PATCH 3/4] fix: match docs examples to actual production responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Initial status is "pending" (not "processing") - retryAfter is 10 in production - Sequence diagram shows pending → processing → completed flow --- async-polling.mdx | 4 ++-- quickstart.mdx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/async-polling.mdx b/async-polling.mdx index b643808..5256608 100644 --- a/async-polling.mdx +++ b/async-polling.mdx @@ -14,10 +14,10 @@ sequenceDiagram participant API Client->>API: POST /v1/graphs/dependency (file + Idempotency-Key) - API-->>Client: 202 Accepted {status: "processing", retryAfter: 5} + API-->>Client: 202 Accepted {status: "pending", retryAfter: 10} Note over Client: Wait retryAfter seconds Client->>API: POST /v1/graphs/dependency (same file + same key) - API-->>Client: 202 Accepted {status: "processing", retryAfter: 5} + API-->>Client: 202 Accepted {status: "processing", retryAfter: 10} Note over Client: Wait retryAfter seconds Client->>API: POST /v1/graphs/dependency (same file + same key) API-->>Client: 200 OK {status: "completed", result: {graph: {...}}} diff --git a/quickstart.mdx b/quickstart.mdx index 94722b4..0c05f41 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -40,9 +40,9 @@ The API returns an **HTTP 202 Accepted** response with the job status: ```json { - "status": "processing", + "status": "pending", "jobId": "550e8400-e29b-41d4-a716-446655440000", - "retryAfter": 5 + "retryAfter": 10 } ``` From 6a83cb311572ba43408ed56e6919f3c40871c097 Mon Sep 17 00:00:00 2001 From: jonathanpopham Date: Sat, 24 Jan 2026 13:37:12 -0500 Subject: [PATCH 4/4] fix: consistent retryAfter value across all examples --- async-polling.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/async-polling.mdx b/async-polling.mdx index 5256608..d9d2df2 100644 --- a/async-polling.mdx +++ b/async-polling.mdx @@ -40,7 +40,7 @@ All graph endpoints return a consistent envelope: { "status": "pending | processing | completed | failed", "jobId": "unique-job-identifier", - "retryAfter": 5, + "retryAfter": 10, "result": { ... }, "error": "error message if failed" }