# Create Agent Question Source: https://wordsmithai.mintlify.app/api-reference/agents/create-question POST /api/v1/agents/{agent_id}/questions Ask a question to the Wordsmith AI agent with optional file attachments Creates a new agent session by asking a question, or adds a question to an existing session. The agent can analyze text, documents, and provide legal insights based on the question and any attached files. When adding questions to an existing session, the conversation context is preserved, allowing for follow-up questions and multi-turn conversations. ## Authentication This endpoint requires a valid API key in the Authorization header. ## Path Parameters **agent\_id** `string` *required* (default: default) The agent to ask. Accepts the agent id shown in the app, the `id` returned by [List Agents](/api-reference/agents/list-agents), or `"default"` for the general Wordsmith agent. ## Request Body **question** `string` *required* The question to ask the agent. Maximum length: 10,000 characters. **attachments** `array` An array of file attachments (maximum 10 files). Each attachment can be either an uploaded file or a URL. **Attachment Object** **upload\_job\_id** `string` The upload job ID returned from the file upload endpoint. Provide exactly one of `upload_job_id` and `url`. **file\_id** `string` **Preview.** A file already in Wordsmith, from the [files API](/api-reference/files/upload-url). The agent reads a copy. Only for organisations in the [MCP and files preview](/preview); others receive `403 mcp_not_enabled`. Provide exactly one of `upload_job_id`, `url` and `file_id`. **url** `string` A publicly accessible URL to download the file. Provide exactly one of `upload_job_id` and `url`. The file must be 100 MB or smaller and download within 20 seconds; all `url` attachments on one question share a 15-second limit. **unzip** `boolean` (default: false) Whether to automatically extract ZIP archives and process individual files within them. **session\_id** `string` The session ID to add this question to. If not provided, a new session will be created. When adding to an existing session, the conversation context is preserved for follow-up questions. **permissions** `object` Optional permissions settings for the chat session. **Permissions Object** **visibility** `string` *required* The visibility level for the chat session. Controls who can access the session: \ • `"private"`: Only you can access the session \ • `"organization"`: Anyone in your organization can access the session \ • `"public"`: Anyone with the link can access the session **callback\_url** `string` URL to receive a webhook notification when async processing is complete. Only used when `sync_mode` is `false`. Optionally signed with the webhook secret if provided when creating a new API key. Signature is in the `Wordsmith-Signature` header. **wait** `integer` (default: 0) **Preview.** Seconds to wait for the answer before responding, from `0` to `20`. The response comes back as soon as the agent answers, so a long `wait` costs nothing on a quick question. Only for organisations in the [MCP and files preview](/preview), where every question also starts an [operation](/api-reference/operations/get-operation). **sync\_mode** `boolean` (default: false) **Deprecated.** Use `wait`, or poll [Get Question Status](/api-reference/agents/get-question-status). Whether to wait for the complete response synchronously. `true`: Response includes the full answer (30-second timeout). `false`: Returns immediately with a session ID for async processing. ## Headers **Idempotency-Key** `string` **Preview.** A unique key for this request, such as a UUID, at most 255 characters. Repeating the request with the same key returns the first question with `200` and asks nothing new. The same key with a different body returns `409 idempotency_key_reused`. A key belongs to the API key or OAuth app and the user that sent it. ```bash cURL - Simple Question theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/agents/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "What are the key elements of a valid contract under US law?", "sync_mode": true }' ``` ```bash cURL - With File Attachment theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/agents/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "Please review this contract and identify any potential issues", "attachments": [ { "upload_job_id": "123e4567-e89b-12d3-a456-426614174000" } ], "sync_mode": false, "callback_url": "https://your-app.com/webhooks/wordsmith" }' ``` ```bash cURL - With URL Attachment theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/agents/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "Analyze this public document", "attachments": [ { "url": "https://example.com/public-document.pdf" } ], "sync_mode": false }' ``` ```bash cURL - Add Question to Existing Session theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/agents/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "Can you elaborate on the liability clause you mentioned?", "session_id": "550e8400-e29b-41d4-a716-446655440000", "sync_mode": false }' ``` ```bash cURL - With Session Permissions theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/agents/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "Review this contract for potential issues", "attachments": [ { "upload_job_id": "123e4567-e89b-12d3-a456-426614174000" } ], "permissions": { "visibility": "organization" }, "sync_mode": false }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.wordsmith.ai/api/v1/agents/default/questions", { method: "POST", headers: { Authorization: "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json", }, body: JSON.stringify({ question: "What are the implications of this clause?", attachments: [ { upload_job_id: "123e4567-e89b-12d3-a456-426614174000", }, ], permissions: { visibility: "organization", }, sync_mode: false, callback_url: "https://your-app.com/webhooks/wordsmith", }), } ); const result = await response.json(); ``` ```python Python theme={null} import requests headers = { "Authorization": "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json" } data = { "question": "Please summarize the key terms in this agreement", "attachments": [ { "upload_job_id": "upload_abc123def456" } ], "permissions": { "visibility": "organization" }, "sync_mode": False, "callback_url": "https://your-app.com/webhooks/wordsmith" } response = requests.post( "https://api.wordsmith.ai/api/v1/agents/default/questions", headers=headers, json=data ) result = response.json() ``` ## Response **id** `string` The unique identifier for this specific question. Always usable to check the question's status, including when `status` is `"filtered"`. **session\_id** `string` The session ID that contains this question. This is always the same for all questions within a conversation session. `null` only when `status` is `"filtered"`, where no session was created. **session\_url** `string` A direct URL to view this session in the Wordsmith web application. This allows users to access the full conversation history and interact with the agent through the web interface. `null` only when `status` is `"filtered"`, where there is no session to view. **status** `string` The current status of the question: `"in_progress"`: Processing is ongoing. `"completed"`: Answer is ready. `"error"`: Processing failed. `"filtered"`: The agent declined the question — see [Filtered questions](#filtered-questions). **answer** `string` The agent's response to your question. Present when `status` is `"completed"`, and also when it is `"filtered"` — where it carries the decline wording, so a client that only reads `answer` still has something to show. See [Filtered questions](#filtered-questions). **attachments** `array` Array of files generated by the agent (e.g., summary documents, analysis reports). Only present when `status` is `"completed"`. **Attachment Object** **file\_name** `string` The name of the generated file **content\_type** `string` The MIME type of the file **content\_length** `integer` The size of the file in bytes **url** `string` A presigned URL to download the file. It expires 5 minutes after the response; poll again for a fresh one. **operation\_id** `string` **Preview.** The [operation](/api-reference/operations/get-operation) tracking this question, for organisations in the [MCP and files preview](/preview). `null` otherwise. Wait on it with `GET /api/v1/operations/{operation_id}?wait=20`. **reason** `string` One sentence explaining why the agent declined the question. Present only when `status` is `"filtered"`, and `null` for every other status. It may also be `null` on a filtered question — see [Filtered questions](#filtered-questions) — so treat it as an explanation to show when you have one, not a field to depend on. ```json Sync Mode - Completed Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "session_id": "123e4567-e89b-12d3-a456-426614174000", "session_url": "https://app.wordsmith.ai/chat/123e4567-e89b-12d3-a456-426614174000", "status": "completed", "answer": "A valid contract under US law typically requires four essential elements:\n\n1. **Offer**: A clear, definite proposal to enter into an agreement...", "attachments": [ { "file_name": "contract_analysis.pdf", "content_type": "application/pdf", "content_length": 245760, "url": "https://files.wordsmith.ai/signed-url-here" } ], "reason": null, "operation_id": null } ``` ```json Async Mode - In Progress Response theme={null} { "id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "session_id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "session_url": "https://app.wordsmith.ai/chat/987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "status": "in_progress", "answer": null, "attachments": null, "reason": null, "operation_id": null } ``` ```json Follow-up Question in Existing Session theme={null} { "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "session_id": "550e8400-e29b-41d4-a716-446655440000", "session_url": "https://app.wordsmith.ai/chat/550e8400-e29b-41d4-a716-446655440000", "status": "in_progress", "answer": null, "attachments": null, "reason": null, "operation_id": null } ``` ```json Error Response theme={null} { "id": "abcdef12-3456-7890-abcd-ef1234567890", "session_id": "abcdef12-3456-7890-abcd-ef1234567890", "session_url": "https://app.wordsmith.ai/chat/abcdef12-3456-7890-abcd-ef1234567890", "status": "error", "answer": "Unable to process the attached file. Please ensure the file is not corrupted and try again.", "attachments": null, "reason": null, "operation_id": null } ``` ```json Filtered Response theme={null} { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "session_id": null, "session_url": null, "status": "filtered", "answer": "This agent only handles commercial contract questions, and this one is about employment law.", "attachments": null, "reason": "This agent only handles commercial contract questions, and this one is about employment law.", "operation_id": null } ``` ## Filtered Questions An agent can be configured with an **intake instruction** describing the work it accepts. When a new question does not match that instruction, the agent declines it: the request returns `status: "filtered"` and no session is created. `"filtered"` is **not an error**. The request was well-formed and authenticated; the agent simply decided the question was not for it. Handle it as its own outcome rather than as a failure to retry. Three things follow from no session existing: * **`session_id` and `session_url` are `null`.** They are null for this status and no other, so existing handling for `"in_progress"`, `"completed"` and `"error"` is unaffected. * **`id` is still valid.** It identifies the filtered question, and `GET /api/v1/agents/{agent_id}/questions/{id}` keeps returning `"filtered"` for it. Nothing about the request is lost. * **`"filtered"` is terminal.** A filtered question never becomes `"in_progress"` or `"completed"`, so polling loops must stop on it — see [Get Question Status](/api-reference/agents/get-question-status#polling-strategy). ### What you get back to show Both `answer` and `reason` carry the decline wording, so a client written against `answer` alone keeps working without knowing the status exists: * **`reason`** is always the agent's own one-sentence explanation of why *this* question was declined — for example, *"This article is about a new UK immigration white paper and salary thresholds for skilled worker visas, not about statutory minimum wage rates."* * **`answer`** is the same sentence, unless the agent's owner has written fixed wording to use instead. Doing so trades the per-question detail for a string you can rely on being identical every time. Both are written for a person, so either is safe to log or show to whoever submitted the question. Neither is a stable identifier: the generated wording differs per question, and the fixed wording can be edited at any time. Branch on `status`, never on this text. > [!NOTE] > > `answer` and `reason` can both be `null` on a `"filtered"` response. Questions filtered before these fields existed have no stored explanation, and one is not reconstructed on later polls. Show the explanation when it is there and fall back to your own copy when it is not. Editing the agent's wording only affects questions filtered afterwards. Whatever a question was answered with is stored when it is declined, so re-polling an old `id` always returns what you were originally told. Only a **new** question is judged. A follow-up sent with `session_id` continues a conversation the agent already accepted and is never filtered. If you supplied a `callback_url`, a `"filtered"` webhook is delivered too, so webhook-driven integrations are not left waiting for a run that will never happen. > [!NOTE] > > An agent only accepts questions while its **API intake** is switched on. If it has been switched off, this endpoint returns `404` — the agent also stops appearing in [List Agents](/api-reference/agents/list-agents). Questions asked before it was switched off remain pollable via [Get Question Status](/api-reference/agents/get-question-status), so an in-flight answer is never stranded. ## Async Processing & Webhooks When using `sync_mode: false`, you can receive notifications via webhook when processing completes: ```json Webhook Payload - Completed theme={null} { "id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "session_id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "session_url": "https://app.wordsmith.ai/chat/987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "status": "completed", "answer": "Based on my analysis of the contract...", "attachments": [ { "file_name": "contract_review.pdf", "content_type": "application/pdf", "content_length": 524288, "url": "https://files.wordsmith.ai/signed-url-here" } ], "reason": null } ``` ```json Webhook Payload - Error theme={null} { "id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "session_id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "session_url": "https://app.wordsmith.ai/chat/987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "status": "error", "answer": "The document format is not supported. Please convert to PDF and try again.", "attachments": null, "reason": null } ``` ```json Webhook Payload - Filtered theme={null} { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "session_id": null, "session_url": null, "status": "filtered", "answer": "This agent only handles commercial contract questions, and this one is about employment law.", "attachments": null, "reason": "This agent only handles commercial contract questions, and this one is about employment law." } ``` For detailed information about webhook setup, signature verification, and security best practices, see the [Webhooks documentation](/webhooks). ## Error Responses See the [Errors](/errors) page for the full error format reference and handling guidance. ```json 400 Bad Request - Invalid Body theme={null} { "error_code": "invalid_request_body", "ws_api_error_code": "invalid_request_body", "message": "Request body is invalid. Ensure it is valid JSON with a 'question' field." } ``` ```json 400 Bad Request - Invalid Upload Job theme={null} { "error_code": "invalid_upload_job", "ws_api_error_code": "invalid_upload_job", "message": "One or more upload_job_id values are invalid or have expired. Generate a new upload URL and re-upload the file." } ``` ```json 400 Bad Request - Session Processing theme={null} { "error_code": "session_still_processing", "ws_api_error_code": "session_still_processing", "message": "Cannot add a new question while a previous question in this session is still processing." } ``` ```json 401 Unauthorized theme={null} { "error_code": "unauthorized", "ws_api_error_code": "unauthorized", "message": "Invalid API key" } ``` ```json 404 Not Found theme={null} { "error_code": "resource_not_found", "ws_api_error_code": "resource_not_found", "message": "Question not found for this agent." } ``` ```json 404 Not Found - Agent Not Exposed theme={null} { "error_code": "resource_not_found", "ws_api_error_code": "resource_not_found", "message": "Agent 'a1b2c3d4' is not available over the API. Enable the agent's API intake to expose it." } ``` ```json 429 Too Many Requests theme={null} { "error_code": "rate_limit_exceeded", "ws_api_error_code": "rate_limit_exceeded", "message": "Rate limit exceeded. Please retry after a short delay." } ``` ## Session Management ### Adding Questions to Existing Sessions When adding questions to an existing session using the `session_id` parameter: * **Conversation Context**: The agent maintains context from previous questions and answers in the session * **Sequential Processing**: New questions cannot be submitted until the previous question in the session has completed processing * **Error Handling**: If a previous question is still processing, the API will return a 400 Bad Request error ### ID vs Session ID The response includes two important identifiers: * **`id`**: The unique identifier for this specific question. For new sessions, this equals the session\_id. For follow-up questions, this is a unique question ID. For a [filtered](#filtered-questions) question it is an identifier of its own, naming a question no session was created for. * **`session_id`**: The session ID that contains this question. This remains constant for all questions within the same conversation, and is `null` when `status` is `"filtered"`. Use the `id` field to check the status of individual questions, and the `session_id` to identify which conversation session the question belongs to. Treat `id` as an opaque string: it is always present and always pollable, but do not assume it equals the `session_id` — that only holds for the first question of an accepted session. ## Best Practices ### Sync vs Async Mode **Use Sync Mode (`sync_mode: true`) for:** * Testing and development **Use Async Mode (`sync_mode: false`) for:** * Complex document analysis * Multiple file attachments * Production applications * Long-form research questions ### File Attachments * **Supported formats**: PDF, DOC, DOCX, TXT, MD, HTML, XLS, XLSX, CSV, TSV, PPT, PPTX, PNG, JPEG, WebP, TIFF, HEIC, HEIF, MP3, MP4, M4A, MPEG, WAV, WebM, ZIP * **Maximum file size**: 1 GB per uploaded file, 100 MB per `url` attachment * **Maximum attachments**: 10 files per question ### Question Guidelines * **Be specific**: More detailed questions get better answers * **Provide context**: Include relevant background information ## Use Cases ### 1. Document Review & Analysis * **Basic Review**: "Review attached document" * **Review with Specific Playbook**: "Review this document using the Standard NDA playbook" * **Reference Playbook by ID**: "Review this document using playbook ID: 123e4567-e89b-12d3-a456-426614174000" ### 2. Template Filling & Document Generation * **Fill Template**: "Fill in our standardemployment agreement template using me as the employer party" * **Reference Template by ID**: "Fill in template ID: 123e4567-e89b-12d3-a456-426614174000 using my company information" ### 3. Legal Research & Analysis * **Case Law Research**: "What are the recent precedents for breach of contract cases in California?" * **Regulatory Compliance**: "What are the current requirements for data protection in healthcare contracts?" * **Legal Framework**: "Explain the key elements of a valid employment contract under UK law" * **Industry Standards**: "What are the standard terms typically included in SaaS vendor agreements?" ### 4. Document Drafting & Creation * **Email Drafting**: "Draft a professional email to a client explaining contract delays" * **Clause Drafting**: "Draft a force majeure clause for a construction contract" * **Legal Letters**: "Write a demand letter for unpaid invoices" * **Meeting Minutes**: "Draft meeting minutes based on the attached audio recording" ### 5. Translation & Conversion * **Language Translation**: "Translate this contract to Spanish while maintaining legal accuracy" * **Multi-language**: "Provide this agreement in both English and French versions" * **Format Conversion**: "Convert this PDF document to DOCX" ### 6. Presentations & Spreadsheets * **Generate Presentation**: "Create a PowerPoint presentation summarizing the key findings from the review of attached NDA" * **Executive Summary**: "Generate an executive summary of this legal document for senior management" * **Questionnaire Processing**: "Fill in this security questionnaire in the attached XLSX" * **Spreadsheet Generation**: "Extract all dates, amounts, and party names from this document into a structured spreadsheet" # Get Question Status Source: https://wordsmithai.mintlify.app/api-reference/agents/get-question-status GET /api/v1/agents/{agent_id}/questions/{question_id} Retrieve the status and results of a previously submitted question Retrieves the current status and results of a question that was submitted to the agent. This endpoint is primarily used to check the progress of async questions or to retrieve results after webhook notifications. ## Authentication This endpoint requires a valid API key in the Authorization header. ## Path Parameters **agent\_id** `string` *required* The agent that was asked. Accepts the agent id shown in the app, the `id` returned by [List Agents](/api-reference/agents/list-agents), or `"default"` for the general Wordsmith agent. **question\_id** `string` *required* The `id` returned when the question was originally created. This addresses that specific question — for the first question in a session it equals the session ID, and for follow-up questions it is a distinct question ID. See [Understanding IDs](#understanding-ids). ```bash cURL theme={null} curl -X GET "https://api.wordsmith.ai/api/v1/agents/default/questions/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.wordsmith.ai/api/v1/agents/default/questions/550e8400-e29b-41d4-a716-446655440000", { method: "GET", headers: { Authorization: "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json", }, } ); const questionStatus = await response.json(); ``` ```python Python theme={null} import requests headers = { "Authorization": "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json" } response = requests.get( "https://api.wordsmith.ai/api/v1/agents/default/questions/550e8400-e29b-41d4-a716-446655440000", headers=headers ) question_status = response.json() ``` ## Response **id** `string` The unique identifier for this specific question. **session\_id** `string` The session ID that contains this question. This remains constant for all questions within the same conversation session. `null` only when `status` is `"filtered"`, where no session was created. **session\_url** `string` A direct URL to view this session in the Wordsmith web application. This allows users to access the full conversation history and interact with the agent through the web interface. `null` only when `status` is `"filtered"`, where there is no session to view. **status** `string` The current status of the question: `"in_progress"`: The agent is still processing your question. `"completed"`: Processing is complete and the answer is available. `"error"`: An error occurred during processing. `"filtered"`: The agent declined the question, so nothing was processed and no session exists — see [Filtered questions](/api-reference/agents/create-question#filtered-questions). `"completed"`, `"error"` and `"filtered"` are all **terminal**: once returned, a question's status never changes again. Only `"in_progress"` is worth polling on. **answer** `string` The agent's response to your question. Present when `status` is `"completed"`, and also when it is `"filtered"` — where it carries the decline wording, so a client that only reads `answer` still has something to show. See [Filtered questions](/api-reference/agents/create-question#filtered-questions). **attachments** `array` Array of files generated by the agent during processing. Only present when `status` is `"completed"` and files were generated. **Attachment Object** **file\_name** `string` The name of the generated file **content\_type** `string` The MIME type of the file (e.g., "application/pdf", "text/plain") **content\_length** `integer` The size of the file in bytes **url** `string` A presigned URL to download the file. It expires 5 minutes after the response; poll again for a fresh one. **reason** `string` One sentence explaining why the agent declined this particular question, stored when it was filtered and returned unchanged on every later poll. Present only when `status` is `"filtered"`, and `null` for every other status. `answer` carries the same sentence, unless the agent's owner has set fixed wording to return instead. Both can be `null` on a filtered question — questions filtered before these fields existed have no stored explanation — so show them when they are there rather than depending on them. ```json In Progress theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "session_id": "550e8400-e29b-41d4-a716-446655440000", "session_url": "https://app.wordsmith.ai/chat/550e8400-e29b-41d4-a716-446655440000", "status": "in_progress", "answer": null, "attachments": null, "reason": null, "operation_id": null } ``` ```json Completed - Text Only theme={null} { "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "session_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "session_url": "https://app.wordsmith.ai/chat/6ba7b810-9dad-11d1-80b4-00c04fd430c8", "status": "completed", "answer": "Based on the contract you provided, I've identified several key areas of concern:\n\n1. **Liability Limitations**: The limitation of liability clause in Section 8.3 may be too broad...", "attachments": null, "reason": null, "operation_id": null } ``` ```json Completed - With Attachments theme={null} { "id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8", "session_id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8", "session_url": "https://app.wordsmith.ai/chat/6ba7b811-9dad-11d1-80b4-00c04fd430c8", "status": "completed", "answer": "I've completed a comprehensive analysis of your document. The key findings are summarized below, and I've also generated a detailed report attached to this response.", "attachments": [ { "file_name": "contract_analysis_report.pdf", "content_type": "application/pdf", "content_length": 1024000, "url": "https://files.wordsmith.ai/download/abc123def456" }, { "file_name": "risk_assessment_summary.docx", "content_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "content_length": 256000, "url": "https://files.wordsmith.ai/download/def456ghi789" } ], "reason": null, "operation_id": null } ``` ```json Error theme={null} { "id": "6ba7b812-9dad-11d1-80b4-00c04fd430c8", "session_id": "6ba7b812-9dad-11d1-80b4-00c04fd430c8", "session_url": "https://app.wordsmith.ai/chat/6ba7b812-9dad-11d1-80b4-00c04fd430c8", "status": "error", "answer": "Unable to process the attached document. The file appears to be corrupted or in an unsupported format. Please check the file and try uploading again.", "attachments": null, "reason": null, "operation_id": null } ``` ```json Filtered theme={null} { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "session_id": null, "session_url": null, "status": "filtered", "answer": "This agent only handles commercial contract questions, and this one is about employment law.", "attachments": null, "reason": "This agent only handles commercial contract questions, and this one is about employment law.", "operation_id": null } ``` ## Understanding IDs ### ID vs Session ID The response includes two important identifiers: * **`id`**: The unique identifier for this specific question. For the first question in a session, this equals the session\_id. For follow-up questions, this is a unique feed item ID. * **`session_id`**: The session ID that contains this question. This remains constant for all questions within the same conversation, and is `null` for a filtered question. **Examples:** * First question in a new session: `id` and `session_id` are the same * Follow-up question: `id` is a unique question ID, `session_id` is the original session ID * [Filtered](/api-reference/agents/create-question#filtered-questions) question: `id` names the question itself, `session_id` is `null` because no session was created Use the `id` field to check the status of individual questions, and the `session_id` to identify which conversation session the question belongs to. ## Polling Strategy When using async mode, you can poll this endpoint to check for completion. Stop on any terminal status — `"completed"`, `"error"` or `"filtered"` — and treat everything else as still in flight. A loop that waits only for `"completed"` or `"error"` polls a filtered question until it times out. Here's a recommended polling strategy: ```javascript JavaScript - Polling Example theme={null} const TERMINAL_STATUSES = ["completed", "error", "filtered"]; async function waitForCompletion(questionId, maxWaitTime = 300000) { // 5 minutes max const startTime = Date.now(); const pollInterval = 2000; // Start with 2 seconds let currentInterval = pollInterval; while (Date.now() - startTime < maxWaitTime) { const response = await fetch( `https://api.wordsmith.ai/api/v1/agents/default/questions/${questionId}`, { headers: { Authorization: "Bearer sk-ws-api1-your_api_key_here" }, } ); const result = await response.json(); // Stop on every terminal status, including "filtered" — a filtered // question never progresses, so waiting on it would spin until timeout. if (TERMINAL_STATUSES.includes(result.status)) { return result; } // Exponential backoff: increase interval up to 30 seconds await new Promise((resolve) => setTimeout(resolve, currentInterval)); currentInterval = Math.min(currentInterval * 1.5, 30000); } throw new Error("Timeout waiting for question completion"); } // Usage try { const result = await waitForCompletion( "550e8400-e29b-41d4-a716-446655440000" ); if (result.status === "filtered") { console.log("The agent declined this question:", result.id); } else { console.log("Question completed:", result); } } catch (error) { console.error("Failed to get result:", error); } ``` ```python Python - Polling Example theme={null} import requests import time TERMINAL_STATUSES = {"completed", "error", "filtered"} def wait_for_completion(question_id, max_wait_time=300): # 5 minutes max start_time = time.time() poll_interval = 2 # Start with 2 seconds current_interval = poll_interval headers = {"Authorization": "Bearer sk-ws-api1-your_api_key_here"} while time.time() - start_time < max_wait_time: response = requests.get( f"https://api.wordsmith.ai/api/v1/agents/default/questions/{question_id}", headers=headers ) result = response.json() # Stop on every terminal status, including "filtered" — a filtered # question never progresses, so waiting on it would spin until timeout. if result["status"] in TERMINAL_STATUSES: return result # Exponential backoff: increase interval up to 30 seconds time.sleep(current_interval) current_interval = min(current_interval * 1.5, 30) raise Exception("Timeout waiting for question completion") # Usage try: result = wait_for_completion("550e8400-e29b-41d4-a716-446655440000") if result["status"] == "filtered": print("The agent declined this question:", result["id"]) else: print("Question completed:", result) except Exception as error: print("Failed to get result:", error) ``` ## Error Responses See the [Errors](/errors) page for the full error format reference. ```json 404 Not Found theme={null} { "error_code": "resource_not_found", "ws_api_error_code": "resource_not_found", "message": "Question not found for this agent." } ``` ```json 401 Unauthorized theme={null} { "error_code": "unauthorized", "ws_api_error_code": "unauthorized", "message": "Invalid API key" } ``` ```json 403 Forbidden theme={null} { "error_code": "forbidden", "ws_api_error_code": "forbidden", "message": "You do not have permission to perform this action." } ``` ## File Download When the response includes attachments, you can download them using the provided URLs: ```bash cURL - Download Attachment theme={null} curl -o "contract_analysis.pdf" "https://files.wordsmith.ai/download/abc123def456" ``` ```javascript JavaScript - Download Attachment theme={null} async function downloadAttachment(attachment) { const response = await fetch(attachment.url); if (!response.ok) { throw new Error(`Failed to download: ${response.statusText}`); } const blob = await response.blob(); // Create download link const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = attachment.file_name || "download"; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } ``` ```python Python - Download Attachment theme={null} import requests def download_attachment(attachment, local_filename=None): if not local_filename: local_filename = attachment.get('file_name', 'download') response = requests.get(attachment['url'], stream=True) response.raise_for_status() with open(local_filename, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) return local_filename ``` ## Best Practices ### Polling Guidelines * **Start with short intervals**: Begin with 2-3 second intervals for quick questions * **Use exponential backoff**: Gradually increase intervals to reduce API calls * **Set reasonable timeouts**: Most questions complete within 2-5 minutes * **Stop on every terminal status**: `"completed"`, `"error"` and `"filtered"` are all final — never poll past one * **Handle errors gracefully**: Always check for error status and handle appropriately ### File Management * **Download promptly**: Attachment URLs expire 5 minutes after the response. Poll again for fresh ones * **Store locally if needed**: Download and store important generated files * **Check file sizes**: Large files may take time to generate and download ### Performance Optimization * **Use webhooks when possible**: More efficient than polling for production applications * **Cache results**: Store completed responses to avoid unnecessary API calls * **Batch requests**: If checking multiple questions, consider batching your requests ## Use Cases * **Status checking**: Monitor progress of long-running document analysis * **Result retrieval**: Get answers after receiving webhook notifications * **Error handling**: Check for and handle processing errors * **File downloads**: Retrieve generated reports and analysis documents * **Integration workflows**: Build automated systems that process legal documents # List Agents Source: https://wordsmithai.mintlify.app/api-reference/agents/list-agents GET /api/v1/agents Get all agents (legal AI specialists) that you have read access to Get a list of all agents available to your account. Each agent is a legal AI specialist set up for a particular kind of legal work. ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/agents' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/agents", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const agents = await response.json(); console.log(agents); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/agents" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } response = requests.get(url, headers=headers) agents = response.json() print(agents) ``` ```json Response theme={null} [ { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Contract Review Agent", "description": "Specialized in reviewing and analyzing legal contracts for compliance and risk assessment", "created_by": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "owners": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, { "id": "66666666-7777-8888-9999-000000000000", "name": "Sam Lee", "email": "sam@example.com" } ], "owner_teams": [ { "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "name": "Commercial" } ], "created_at": "2026-03-22T17:00:00Z", "url": "https://app.wordsmith.ai/chat?agent=8f1c2d4e-6a7b-4c9d-8e0f-1a2b3c4d5e6f" }, { "id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "name": "Legal Research Agent", "description": "Expert in conducting comprehensive legal research and case law analysis", "created_by": null, "owners": [], "owner_teams": [], "created_at": "2025-11-04T09:30:00Z", "url": null } ] ``` ## Response Fields **id** `string` The unique identifier for the agent **name** `string` The display name of the agent **description** `string` A description of the agent's capabilities and specializations **created\_by** `object` The person who created the agent. `null` for global and repository agents, which record no creator, and when the user no longer exists in your organisation. A creator who has since been removed from the organisation is still returned. **User Object** **id** `string` The user's id, as [List Users](/api-reference/directory/list-users) returns it. **name** `string` The user's display name. **email** `string` The user's email address. **owners** `array` People who hold the Owner role on the agent: they can edit, share and delete it. The creator holds it from the start, and an owner can grant it to others. Lists only active members of your organisation. Empty for global and repository agents. **User Object** **id** `string` The user's id, as [List Users](/api-reference/directory/list-users) returns it. **name** `string` The user's display name. **email** `string` The user's email address. **owner\_teams** `array` Teams that hold the Owner role on the agent. Empty when no team does. **Team Object** **id** `string` The team's id. **name** `string` The team's name. **created\_at** `string` When the agent was created, as an ISO 8601 UTC timestamp such as `2026-03-22T17:00:00Z`. **url** `string | null` Opens the agent in Wordsmith. `null` for a workspace-only agent, which has no agent page. ## Notes * Only agents that you have read access to will be returned * `created_by`, `owners` and `owner_teams` name the same people and teams as the agent's sharing settings in Wordsmith, and never anyone outside your organisation * The list includes both your organization's custom agents and any global agents you have access to * Use the `id` field when creating questions with specific agents via the [Create Question](/api-reference/agents/create-question) endpoint # Create Assistant Question Source: https://wordsmithai.mintlify.app/api-reference/assistants/create-question POST /api/v1/assistants/{assistant_id}/questions Ask a question to the Wordsmith AI assistant with optional file attachments > [!WARNING] > > This endpoint is deprecated. Use [Create Question](/api-reference/agents/create-question) under `/api/v1/agents` instead; the request and response are identical. Responses from this route carry `Deprecation` and `Link` headers. No removal date has been set. Creates a new assistant session by asking a question, or adds a question to an existing session. The assistant can analyze text, documents, and provide legal insights based on the question and any attached files. When adding questions to an existing session, the conversation context is preserved, allowing for follow-up questions and multi-turn conversations. ## Authentication This endpoint requires a valid API key in the Authorization header. ## Path Parameters **assistant\_id** `string` *required* (default: default) The ID of the assistant to use. Use `"default"` for the general Wordsmith assistant, or specify a custom assistant ID if available. ## Request Body **question** `string` *required* The question to ask the assistant. Maximum length: 10,000 characters. **attachments** `array` An array of file attachments (maximum 10 files). Each attachment can be either an uploaded file or a URL. **Attachment Object** **upload\_job\_id** `string` The upload job ID returned from the file upload endpoint. Provide exactly one of `upload_job_id` and `url`. **url** `string` A publicly accessible URL to download the file. Provide exactly one of `upload_job_id` and `url`. The file must be 100 MB or smaller and download within 20 seconds; all `url` attachments on one question share a 15-second limit. **unzip** `boolean` (default: false) Whether to automatically extract ZIP archives and process individual files within them. **session\_id** `string` The session ID to add this question to. If not provided, a new session will be created. When adding to an existing session, the conversation context is preserved for follow-up questions. **permissions** `object` Optional permissions settings for the chat session. **Permissions Object** **visibility** `string` *required* The visibility level for the chat session. Controls who can access the session: \ • `"private"`: Only you can access the session \ • `"organization"`: Anyone in your organization can access the session \ • `"public"`: Anyone with the link can access the session **callback\_url** `string` URL to receive a webhook notification when async processing is complete. Only used when `sync_mode` is `false`. Optionally signed with the webhook secret if provided when creating a new API key. Signature is in the `Wordsmith-Signature` header. **sync\_mode** `boolean` (default: false) Whether to wait for the complete response synchronously. `true`: Response includes the full answer (30-second timeout). `false`: Returns immediately with a session ID for async processing. ```bash cURL - Simple Question theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/assistants/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "What are the key elements of a valid contract under US law?", "sync_mode": true }' ``` ```bash cURL - With File Attachment theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/assistants/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "Please review this contract and identify any potential issues", "attachments": [ { "upload_job_id": "123e4567-e89b-12d3-a456-426614174000" } ], "sync_mode": false, "callback_url": "https://your-app.com/webhooks/wordsmith" }' ``` ```bash cURL - With URL Attachment theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/assistants/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "Analyze this public document", "attachments": [ { "url": "https://example.com/public-document.pdf" } ], "sync_mode": false }' ``` ```bash cURL - Add Question to Existing Session theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/assistants/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "Can you elaborate on the liability clause you mentioned?", "session_id": "550e8400-e29b-41d4-a716-446655440000", "sync_mode": false }' ``` ```bash cURL - With Session Permissions theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/assistants/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "Review this contract for potential issues", "attachments": [ { "upload_job_id": "123e4567-e89b-12d3-a456-426614174000" } ], "permissions": { "visibility": "organization" }, "sync_mode": false }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.wordsmith.ai/api/v1/assistants/default/questions", { method: "POST", headers: { Authorization: "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json", }, body: JSON.stringify({ question: "What are the implications of this clause?", attachments: [ { upload_job_id: "123e4567-e89b-12d3-a456-426614174000", }, ], permissions: { visibility: "organization", }, sync_mode: false, callback_url: "https://your-app.com/webhooks/wordsmith", }), } ); const result = await response.json(); ``` ```python Python theme={null} import requests headers = { "Authorization": "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json" } data = { "question": "Please summarize the key terms in this agreement", "attachments": [ { "upload_job_id": "upload_abc123def456" } ], "permissions": { "visibility": "organization" }, "sync_mode": False, "callback_url": "https://your-app.com/webhooks/wordsmith" } response = requests.post( "https://api.wordsmith.ai/api/v1/assistants/default/questions", headers=headers, json=data ) result = response.json() ``` ## Response **id** `string` The unique identifier for this specific question. Always usable to check the question's status, including when `status` is `"filtered"`. **session\_id** `string` The session ID that contains this question. This is always the same for all questions within a conversation session. `null` only when `status` is `"filtered"`, where no session was created. **session\_url** `string` A direct URL to view this session in the Wordsmith web application. This allows users to access the full conversation history and interact with the assistant through the web interface. `null` only when `status` is `"filtered"`, where there is no session to view. **status** `string` The current status of the question: `"in_progress"`: Processing is ongoing. `"completed"`: Answer is ready. `"error"`: Processing failed. `"filtered"`: The assistant declined the question — see [Filtered questions](#filtered-questions). **answer** `string` The assistant's response to your question. Present when `status` is `"completed"`, and also when it is `"filtered"` — where it carries the decline wording, so a client that only reads `answer` still has something to show. See [Filtered questions](#filtered-questions). **attachments** `array` Array of files generated by the assistant (e.g., summary documents, analysis reports). Only present when `status` is `"completed"`. **Attachment Object** **file\_name** `string` The name of the generated file **content\_type** `string` The MIME type of the file **content\_length** `integer` The size of the file in bytes **url** `string` A presigned URL to download the file. It expires 5 minutes after the response; poll again for a fresh one. **reason** `string` One sentence explaining why the assistant declined the question. Present only when `status` is `"filtered"`, and `null` for every other status. It may also be `null` on a filtered question — see [Filtered questions](#filtered-questions) — so treat it as an explanation to show when you have one, not a field to depend on. ```json Sync Mode - Completed Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "session_id": "123e4567-e89b-12d3-a456-426614174000", "session_url": "https://app.wordsmith.ai/chat/123e4567-e89b-12d3-a456-426614174000", "status": "completed", "answer": "A valid contract under US law typically requires four essential elements:\n\n1. **Offer**: A clear, definite proposal to enter into an agreement...", "attachments": [ { "file_name": "contract_analysis.pdf", "content_type": "application/pdf", "content_length": 245760, "url": "https://files.wordsmith.ai/signed-url-here" } ], "reason": null, "operation_id": null } ``` ```json Async Mode - In Progress Response theme={null} { "id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "session_id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "session_url": "https://app.wordsmith.ai/chat/987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "status": "in_progress", "answer": null, "attachments": null, "reason": null, "operation_id": null } ``` ```json Follow-up Question in Existing Session theme={null} { "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "session_id": "550e8400-e29b-41d4-a716-446655440000", "session_url": "https://app.wordsmith.ai/chat/550e8400-e29b-41d4-a716-446655440000", "status": "in_progress", "answer": null, "attachments": null, "reason": null, "operation_id": null } ``` ```json Error Response theme={null} { "id": "abcdef12-3456-7890-abcd-ef1234567890", "session_id": "abcdef12-3456-7890-abcd-ef1234567890", "session_url": "https://app.wordsmith.ai/chat/abcdef12-3456-7890-abcd-ef1234567890", "status": "error", "answer": "Unable to process the attached file. Please ensure the file is not corrupted and try again.", "attachments": null, "reason": null, "operation_id": null } ``` ```json Filtered Response theme={null} { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "session_id": null, "session_url": null, "status": "filtered", "answer": "This assistant only handles commercial contract questions, and this one is about employment law.", "attachments": null, "reason": "This assistant only handles commercial contract questions, and this one is about employment law.", "operation_id": null } ``` ## Filtered Questions An assistant can be configured with an **intake instruction** describing the work it accepts. When a new question does not match that instruction, the assistant declines it: the request returns `status: "filtered"` and no session is created. `"filtered"` is **not an error**. The request was well-formed and authenticated; the assistant simply decided the question was not for it. Handle it as its own outcome rather than as a failure to retry. Three things follow from no session existing: * **`session_id` and `session_url` are `null`.** They are null for this status and no other, so existing handling for `"in_progress"`, `"completed"` and `"error"` is unaffected. * **`id` is still valid.** It identifies the filtered question, and `GET /api/v1/assistants/{assistant_id}/questions/{id}` keeps returning `"filtered"` for it. Nothing about the request is lost. * **`"filtered"` is terminal.** A filtered question never becomes `"in_progress"` or `"completed"`, so polling loops must stop on it — see [Get Question Status](/api-reference/assistants/get-question-status#polling-strategy). ### What you get back to show Both `answer` and `reason` carry the decline wording, so a client written against `answer` alone keeps working without knowing the status exists: * **`reason`** is always the assistant's own one-sentence explanation of why *this* question was declined — for example, *"This article is about a new UK immigration white paper and salary thresholds for skilled worker visas, not about statutory minimum wage rates."* * **`answer`** is the same sentence, unless the assistant's owner has written fixed wording to use instead. Doing so trades the per-question detail for a string you can rely on being identical every time. Both are written for a person, so either is safe to log or show to whoever submitted the question. Neither is a stable identifier: the generated wording differs per question, and the fixed wording can be edited at any time. Branch on `status`, never on this text. > [!NOTE] > > `answer` and `reason` can both be `null` on a `"filtered"` response. Questions filtered before these fields existed have no stored explanation, and one is not reconstructed on later polls. Show the explanation when it is there and fall back to your own copy when it is not. Editing the assistant's wording only affects questions filtered afterwards. Whatever a question was answered with is stored when it is declined, so re-polling an old `id` always returns what you were originally told. Only a **new** question is judged. A follow-up sent with `session_id` continues a conversation the assistant already accepted and is never filtered. If you supplied a `callback_url`, a `"filtered"` webhook is delivered too, so webhook-driven integrations are not left waiting for a run that will never happen. > [!NOTE] > > An agent-backed assistant only accepts questions while its **API intake** is switched on. If it has been switched off, this endpoint returns `404` — the assistant also stops appearing in [List Assistants](/api-reference/assistants/list-assistants). Questions asked before it was switched off remain pollable via [Get Question Status](/api-reference/assistants/get-question-status), so an in-flight answer is never stranded. ## Async Processing & Webhooks When using `sync_mode: false`, you can receive notifications via webhook when processing completes: ```json Webhook Payload - Completed theme={null} { "id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "session_id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "session_url": "https://app.wordsmith.ai/chat/987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "status": "completed", "answer": "Based on my analysis of the contract...", "attachments": [ { "file_name": "contract_review.pdf", "content_type": "application/pdf", "content_length": 524288, "url": "https://files.wordsmith.ai/signed-url-here" } ], "reason": null } ``` ```json Webhook Payload - Error theme={null} { "id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "session_id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "session_url": "https://app.wordsmith.ai/chat/987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "status": "error", "answer": "The document format is not supported. Please convert to PDF and try again.", "attachments": null, "reason": null } ``` ```json Webhook Payload - Filtered theme={null} { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "session_id": null, "session_url": null, "status": "filtered", "answer": "This assistant only handles commercial contract questions, and this one is about employment law.", "attachments": null, "reason": "This assistant only handles commercial contract questions, and this one is about employment law." } ``` For detailed information about webhook setup, signature verification, and security best practices, see the [Webhooks documentation](/webhooks). ## Error Responses See the [Errors](/errors) page for the full error format reference and handling guidance. ```json 400 Bad Request - Invalid Body theme={null} { "error_code": "invalid_request_body", "ws_api_error_code": "invalid_request_body", "message": "Request body is invalid. Ensure it is valid JSON with a 'question' field." } ``` ```json 400 Bad Request - Invalid Upload Job theme={null} { "error_code": "invalid_upload_job", "ws_api_error_code": "invalid_upload_job", "message": "One or more upload_job_id values are invalid or have expired. Generate a new upload URL and re-upload the file." } ``` ```json 400 Bad Request - Session Processing theme={null} { "error_code": "session_still_processing", "ws_api_error_code": "session_still_processing", "message": "Cannot add a new question while a previous question in this session is still processing." } ``` ```json 401 Unauthorized theme={null} { "error_code": "unauthorized", "ws_api_error_code": "unauthorized", "message": "Invalid API key" } ``` ```json 404 Not Found theme={null} { "error_code": "resource_not_found", "ws_api_error_code": "resource_not_found", "message": "Question not found for this agent." } ``` ```json 404 Not Found - Assistant Not Exposed theme={null} { "error_code": "resource_not_found", "ws_api_error_code": "resource_not_found", "message": "Agent 'a1b2c3d4' is not available over the API. Enable the agent's API intake to expose it." } ``` ```json 429 Too Many Requests theme={null} { "error_code": "rate_limit_exceeded", "ws_api_error_code": "rate_limit_exceeded", "message": "Rate limit exceeded. Please retry after a short delay." } ``` ## Session Management ### Adding Questions to Existing Sessions When adding questions to an existing session using the `session_id` parameter: * **Conversation Context**: The assistant maintains context from previous questions and answers in the session * **Sequential Processing**: New questions cannot be submitted until the previous question in the session has completed processing * **Error Handling**: If a previous question is still processing, the API will return a 400 Bad Request error ### ID vs Session ID The response includes two important identifiers: * **`id`**: The unique identifier for this specific question. For new sessions, this equals the session\_id. For follow-up questions, this is a unique question ID. For a [filtered](#filtered-questions) question it is an identifier of its own, naming a question no session was created for. * **`session_id`**: The session ID that contains this question. This remains constant for all questions within the same conversation, and is `null` when `status` is `"filtered"`. Use the `id` field to check the status of individual questions, and the `session_id` to identify which conversation session the question belongs to. Treat `id` as an opaque string: it is always present and always pollable, but do not assume it equals the `session_id` — that only holds for the first question of an accepted session. ## Best Practices ### Sync vs Async Mode **Use Sync Mode (`sync_mode: true`) for:** * Testing and development **Use Async Mode (`sync_mode: false`) for:** * Complex document analysis * Multiple file attachments * Production applications * Long-form research questions ### File Attachments * **Supported formats**: PDF, DOC, DOCX, TXT, MD, HTML, XLS, XLSX, CSV, TSV, PPT, PPTX, PNG, JPEG, WebP, TIFF, HEIC, HEIF, MP3, MP4, M4A, MPEG, WAV, WebM, ZIP * **Maximum file size**: 1 GB per uploaded file, 100 MB per `url` attachment * **Maximum attachments**: 10 files per question ### Question Guidelines * **Be specific**: More detailed questions get better answers * **Provide context**: Include relevant background information ## Use Cases ### 1. Document Review & Analysis * **Basic Review**: "Review attached document" * **Review with Specific Playbook**: "Review this document using the Standard NDA playbook" * **Reference Playbook by ID**: "Review this document using playbook ID: 123e4567-e89b-12d3-a456-426614174000" ### 2. Template Filling & Document Generation * **Fill Template**: "Fill in our standardemployment agreement template using me as the employer party" * **Reference Template by ID**: "Fill in template ID: 123e4567-e89b-12d3-a456-426614174000 using my company information" ### 3. Legal Research & Analysis * **Case Law Research**: "What are the recent precedents for breach of contract cases in California?" * **Regulatory Compliance**: "What are the current requirements for data protection in healthcare contracts?" * **Legal Framework**: "Explain the key elements of a valid employment contract under UK law" * **Industry Standards**: "What are the standard terms typically included in SaaS vendor agreements?" ### 4. Document Drafting & Creation * **Email Drafting**: "Draft a professional email to a client explaining contract delays" * **Clause Drafting**: "Draft a force majeure clause for a construction contract" * **Legal Letters**: "Write a demand letter for unpaid invoices" * **Meeting Minutes**: "Draft meeting minutes based on the attached audio recording" ### 5. Translation & Conversion * **Language Translation**: "Translate this contract to Spanish while maintaining legal accuracy" * **Multi-language**: "Provide this agreement in both English and French versions" * **Format Conversion**: "Convert this PDF document to DOCX" ### 6. Presentations & Spreadsheets * **Generate Presentation**: "Create a PowerPoint presentation summarizing the key findings from the review of attached NDA" * **Executive Summary**: "Generate an executive summary of this legal document for senior management" * **Questionnaire Processing**: "Fill in this security questionnaire in the attached XLSX" * **Spreadsheet Generation**: "Extract all dates, amounts, and party names from this document into a structured spreadsheet" # Get Question Status Source: https://wordsmithai.mintlify.app/api-reference/assistants/get-question-status GET /api/v1/assistants/{assistant_id}/questions/{question_id} Retrieve the status and results of a previously submitted question > [!WARNING] > > This endpoint is deprecated. Use [Get Question Status](/api-reference/agents/get-question-status) under `/api/v1/agents` instead; the request and response are identical. Responses from this route carry `Deprecation` and `Link` headers. No removal date has been set. Retrieves the current status and results of a question that was submitted to the assistant. This endpoint is primarily used to check the progress of async questions or to retrieve results after webhook notifications. ## Authentication This endpoint requires a valid API key in the Authorization header. ## Path Parameters **assistant\_id** `string` *required* The ID of the assistant that processed the question. Use `"default"` for the general Wordsmith assistant, or the specific assistant ID used in the original request. **question\_id** `string` *required* The `id` returned when the question was originally created. This addresses that specific question — for the first question in a session it equals the session ID, and for follow-up questions it is a distinct question ID. See [Understanding IDs](#understanding-ids). ```bash cURL theme={null} curl -X GET "https://api.wordsmith.ai/api/v1/assistants/default/questions/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.wordsmith.ai/api/v1/assistants/default/questions/550e8400-e29b-41d4-a716-446655440000", { method: "GET", headers: { Authorization: "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json", }, } ); const questionStatus = await response.json(); ``` ```python Python theme={null} import requests headers = { "Authorization": "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json" } response = requests.get( "https://api.wordsmith.ai/api/v1/assistants/default/questions/550e8400-e29b-41d4-a716-446655440000", headers=headers ) question_status = response.json() ``` ## Response **id** `string` The unique identifier for this specific question. **session\_id** `string` The session ID that contains this question. This remains constant for all questions within the same conversation session. `null` only when `status` is `"filtered"`, where no session was created. **session\_url** `string` A direct URL to view this session in the Wordsmith web application. This allows users to access the full conversation history and interact with the assistant through the web interface. `null` only when `status` is `"filtered"`, where there is no session to view. **status** `string` The current status of the question: `"in_progress"`: The assistant is still processing your question. `"completed"`: Processing is complete and the answer is available. `"error"`: An error occurred during processing. `"filtered"`: The assistant declined the question, so nothing was processed and no session exists — see [Filtered questions](/api-reference/assistants/create-question#filtered-questions). `"completed"`, `"error"` and `"filtered"` are all **terminal**: once returned, a question's status never changes again. Only `"in_progress"` is worth polling on. **answer** `string` The assistant's response to your question. Present when `status` is `"completed"`, and also when it is `"filtered"` — where it carries the decline wording, so a client that only reads `answer` still has something to show. See [Filtered questions](/api-reference/assistants/create-question#filtered-questions). **attachments** `array` Array of files generated by the assistant during processing. Only present when `status` is `"completed"` and files were generated. **Attachment Object** **file\_name** `string` The name of the generated file **content\_type** `string` The MIME type of the file (e.g., "application/pdf", "text/plain") **content\_length** `integer` The size of the file in bytes **url** `string` A presigned URL to download the file. It expires 5 minutes after the response; poll again for a fresh one. **reason** `string` One sentence explaining why the assistant declined this particular question, stored when it was filtered and returned unchanged on every later poll. Present only when `status` is `"filtered"`, and `null` for every other status. `answer` carries the same sentence, unless the assistant's owner has set fixed wording to return instead. Both can be `null` on a filtered question — questions filtered before these fields existed have no stored explanation — so show them when they are there rather than depending on them. ```json In Progress theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "session_id": "550e8400-e29b-41d4-a716-446655440000", "session_url": "https://app.wordsmith.ai/chat/550e8400-e29b-41d4-a716-446655440000", "status": "in_progress", "answer": null, "attachments": null, "reason": null, "operation_id": null } ``` ```json Completed - Text Only theme={null} { "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "session_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "session_url": "https://app.wordsmith.ai/chat/6ba7b810-9dad-11d1-80b4-00c04fd430c8", "status": "completed", "answer": "Based on the contract you provided, I've identified several key areas of concern:\n\n1. **Liability Limitations**: The limitation of liability clause in Section 8.3 may be too broad...", "attachments": null, "reason": null, "operation_id": null } ``` ```json Completed - With Attachments theme={null} { "id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8", "session_id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8", "session_url": "https://app.wordsmith.ai/chat/6ba7b811-9dad-11d1-80b4-00c04fd430c8", "status": "completed", "answer": "I've completed a comprehensive analysis of your document. The key findings are summarized below, and I've also generated a detailed report attached to this response.", "attachments": [ { "file_name": "contract_analysis_report.pdf", "content_type": "application/pdf", "content_length": 1024000, "url": "https://files.wordsmith.ai/download/abc123def456" }, { "file_name": "risk_assessment_summary.docx", "content_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "content_length": 256000, "url": "https://files.wordsmith.ai/download/def456ghi789" } ], "reason": null, "operation_id": null } ``` ```json Error theme={null} { "id": "6ba7b812-9dad-11d1-80b4-00c04fd430c8", "session_id": "6ba7b812-9dad-11d1-80b4-00c04fd430c8", "session_url": "https://app.wordsmith.ai/chat/6ba7b812-9dad-11d1-80b4-00c04fd430c8", "status": "error", "answer": "Unable to process the attached document. The file appears to be corrupted or in an unsupported format. Please check the file and try uploading again.", "attachments": null, "reason": null, "operation_id": null } ``` ```json Filtered theme={null} { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "session_id": null, "session_url": null, "status": "filtered", "answer": "This assistant only handles commercial contract questions, and this one is about employment law.", "attachments": null, "reason": "This assistant only handles commercial contract questions, and this one is about employment law.", "operation_id": null } ``` ## Understanding IDs ### ID vs Session ID The response includes two important identifiers: * **`id`**: The unique identifier for this specific question. For the first question in a session, this equals the session\_id. For follow-up questions, this is a unique feed item ID. * **`session_id`**: The session ID that contains this question. This remains constant for all questions within the same conversation, and is `null` for a filtered question. **Examples:** * First question in a new session: `id` and `session_id` are the same * Follow-up question: `id` is a unique question ID, `session_id` is the original session ID * [Filtered](/api-reference/assistants/create-question#filtered-questions) question: `id` names the question itself, `session_id` is `null` because no session was created Use the `id` field to check the status of individual questions, and the `session_id` to identify which conversation session the question belongs to. ## Polling Strategy When using async mode, you can poll this endpoint to check for completion. Stop on any terminal status — `"completed"`, `"error"` or `"filtered"` — and treat everything else as still in flight. A loop that waits only for `"completed"` or `"error"` polls a filtered question until it times out. Here's a recommended polling strategy: ```javascript JavaScript - Polling Example theme={null} const TERMINAL_STATUSES = ["completed", "error", "filtered"]; async function waitForCompletion(questionId, maxWaitTime = 300000) { // 5 minutes max const startTime = Date.now(); const pollInterval = 2000; // Start with 2 seconds let currentInterval = pollInterval; while (Date.now() - startTime < maxWaitTime) { const response = await fetch( `https://api.wordsmith.ai/api/v1/assistants/default/questions/${questionId}`, { headers: { Authorization: "Bearer sk-ws-api1-your_api_key_here" }, } ); const result = await response.json(); // Stop on every terminal status, including "filtered" — a filtered // question never progresses, so waiting on it would spin until timeout. if (TERMINAL_STATUSES.includes(result.status)) { return result; } // Exponential backoff: increase interval up to 30 seconds await new Promise((resolve) => setTimeout(resolve, currentInterval)); currentInterval = Math.min(currentInterval * 1.5, 30000); } throw new Error("Timeout waiting for question completion"); } // Usage try { const result = await waitForCompletion( "550e8400-e29b-41d4-a716-446655440000" ); if (result.status === "filtered") { console.log("The assistant declined this question:", result.id); } else { console.log("Question completed:", result); } } catch (error) { console.error("Failed to get result:", error); } ``` ```python Python - Polling Example theme={null} import requests import time TERMINAL_STATUSES = {"completed", "error", "filtered"} def wait_for_completion(question_id, max_wait_time=300): # 5 minutes max start_time = time.time() poll_interval = 2 # Start with 2 seconds current_interval = poll_interval headers = {"Authorization": "Bearer sk-ws-api1-your_api_key_here"} while time.time() - start_time < max_wait_time: response = requests.get( f"https://api.wordsmith.ai/api/v1/assistants/default/questions/{question_id}", headers=headers ) result = response.json() # Stop on every terminal status, including "filtered" — a filtered # question never progresses, so waiting on it would spin until timeout. if result["status"] in TERMINAL_STATUSES: return result # Exponential backoff: increase interval up to 30 seconds time.sleep(current_interval) current_interval = min(current_interval * 1.5, 30) raise Exception("Timeout waiting for question completion") # Usage try: result = wait_for_completion("550e8400-e29b-41d4-a716-446655440000") if result["status"] == "filtered": print("The assistant declined this question:", result["id"]) else: print("Question completed:", result) except Exception as error: print("Failed to get result:", error) ``` ## Error Responses See the [Errors](/errors) page for the full error format reference. ```json 404 Not Found theme={null} { "error_code": "resource_not_found", "ws_api_error_code": "resource_not_found", "message": "Question not found for this agent." } ``` ```json 401 Unauthorized theme={null} { "error_code": "unauthorized", "ws_api_error_code": "unauthorized", "message": "Invalid API key" } ``` ```json 403 Forbidden theme={null} { "error_code": "forbidden", "ws_api_error_code": "forbidden", "message": "You do not have permission to perform this action." } ``` ## File Download When the response includes attachments, you can download them using the provided URLs: ```bash cURL - Download Attachment theme={null} curl -o "contract_analysis.pdf" "https://files.wordsmith.ai/download/abc123def456" ``` ```javascript JavaScript - Download Attachment theme={null} async function downloadAttachment(attachment) { const response = await fetch(attachment.url); if (!response.ok) { throw new Error(`Failed to download: ${response.statusText}`); } const blob = await response.blob(); // Create download link const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = attachment.file_name || "download"; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } ``` ```python Python - Download Attachment theme={null} import requests def download_attachment(attachment, local_filename=None): if not local_filename: local_filename = attachment.get('file_name', 'download') response = requests.get(attachment['url'], stream=True) response.raise_for_status() with open(local_filename, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) return local_filename ``` ## Best Practices ### Polling Guidelines * **Start with short intervals**: Begin with 2-3 second intervals for quick questions * **Use exponential backoff**: Gradually increase intervals to reduce API calls * **Set reasonable timeouts**: Most questions complete within 2-5 minutes * **Stop on every terminal status**: `"completed"`, `"error"` and `"filtered"` are all final — never poll past one * **Handle errors gracefully**: Always check for error status and handle appropriately ### File Management * **Download promptly**: Attachment URLs expire 5 minutes after the response. Poll again for fresh ones * **Store locally if needed**: Download and store important generated files * **Check file sizes**: Large files may take time to generate and download ### Performance Optimization * **Use webhooks when possible**: More efficient than polling for production applications * **Cache results**: Store completed responses to avoid unnecessary API calls * **Batch requests**: If checking multiple questions, consider batching your requests ## Use Cases * **Status checking**: Monitor progress of long-running document analysis * **Result retrieval**: Get answers after receiving webhook notifications * **Error handling**: Check for and handle processing errors * **File downloads**: Retrieve generated reports and analysis documents * **Integration workflows**: Build automated systems that process legal documents # List Assistants Source: https://wordsmithai.mintlify.app/api-reference/assistants/list-assistants GET /api/v1/assistants Get all assistants (legal AI specialists) that you have read access to > [!WARNING] > > This endpoint is deprecated. Use [List Agents](/api-reference/agents/list-agents) under `/api/v1/agents` instead; the request and response are identical. Responses from this route carry `Deprecation` and `Link` headers. No removal date has been set. Get a list of all assistants available to your account. Assistants are specialized legal AI agents that can help with specific types of legal work. ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/assistants' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/assistants", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const assistants = await response.json(); console.log(assistants); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/assistants" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } response = requests.get(url, headers=headers) assistants = response.json() print(assistants) ``` ```json Response theme={null} [ { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Contract Review Assistant", "description": "Specialized in reviewing and analyzing legal contracts for compliance and risk assessment", "created_by": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "owners": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, { "id": "66666666-7777-8888-9999-000000000000", "name": "Sam Lee", "email": "sam@example.com" } ], "owner_teams": [ { "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "name": "Commercial" } ], "created_at": "2026-03-22T17:00:00Z", "url": "https://app.wordsmith.ai/chat?agent=8f1c2d4e-6a7b-4c9d-8e0f-1a2b3c4d5e6f" }, { "id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "name": "Legal Research Assistant", "description": "Expert in conducting comprehensive legal research and case law analysis", "created_by": null, "owners": [], "owner_teams": [], "created_at": "2025-11-04T09:30:00Z", "url": null } ] ``` ## Response Fields **id** `string` The unique identifier for the assistant **name** `string` The display name of the assistant **description** `string` A description of the assistant's capabilities and specializations **created\_by** `object` The person who created the assistant. `null` for global and repository assistants, which record no creator, and when the user no longer exists in your organisation. A creator who has since been removed from the organisation is still returned. **User Object** **id** `string` The user's id, as [List Users](/api-reference/directory/list-users) returns it. **name** `string` The user's display name. **email** `string` The user's email address. **owners** `array` People who hold the Owner role on the assistant: they can edit, share and delete it. The creator holds it from the start, and an owner can grant it to others. Lists only active members of your organisation. Empty for global and repository assistants. **User Object** **id** `string` The user's id, as [List Users](/api-reference/directory/list-users) returns it. **name** `string` The user's display name. **email** `string` The user's email address. **owner\_teams** `array` Teams that hold the Owner role on the assistant. Empty when no team does. **Team Object** **id** `string` The team's id. **name** `string` The team's name. **created\_at** `string` When the assistant was created, as an ISO 8601 UTC timestamp such as `2026-03-22T17:00:00Z`. **url** `string | null` Opens the agent in Wordsmith. `null` for a workspace-only agent, which has no agent page. ## Notes * Only assistants that you have read access to will be returned * `created_by`, `owners` and `owner_teams` name the same people and teams as the assistant's sharing settings in Wordsmith, and never anyone outside your organisation * The list includes both your organization's custom assistants and any global assistants you have access to * Use the `id` field when creating questions with specific assistants via the [Create Question](/api-reference/assistants/create-question) endpoint # List Teams Source: https://wordsmithai.mintlify.app/api-reference/directory/list-teams GET /api/v1/teams List the teams in your organization. Team ids are what `assigned_team_id` and the sharing endpoints expect. Teams can nest: `parent_team_id` is set on a sub-team, `member_count` counts the whole roster including sub-teams, and `direct_member_count` counts only this team's own members. `member_count`, `direct_member_count` and `member_ids` include active users only. Suspended and deleted users are left out, though `GET /api/v1/users` still lists suspended users. ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/teams' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/teams", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/teams" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.get(url, headers=headers) print(response.json()) ``` ```json Response theme={null} [ { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal", "member_count": 8, "direct_member_count": 5, "parent_team_id": null, "member_ids": [ "11111111-2222-3333-4444-555555555555" ] } ] ``` # List Users Source: https://wordsmithai.mintlify.app/api-reference/directory/list-users GET /api/v1/users List the people and agents in your organization. Use this to resolve the ids on a task, or to find someone to assign work to. `kind` is `human` or `agent`. An `agent` row's `id` is the agent's directory identity, not the value the tasks API takes: assign an agent through `assigned_agent` with the id from [List Agents](/api-reference/agents/list-agents), or the agent's name or shortcut. ## Query parameters **active\_only** `string` `true` to exclude suspended accounts. **human\_only** `string` `true` to exclude agents. ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/users' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/users", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/users" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.get(url, headers=headers) print(response.json()) ``` ```json Response theme={null} [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "kind": "human", "is_suspended": false } ] ``` # Generate File Upload URL Source: https://wordsmithai.mintlify.app/api-reference/files/upload-url POST /api/v1/files/upload-url Generate a presigned URL for uploading files to use with an agent or a task Generates a presigned URL that can be used to upload files directly to Wordsmith's storage. This is the first step when you want to include file attachments with agent questions, or attach files to a task. ## Authentication This endpoint requires a valid API key in the Authorization header. ## Request Body **file\_name** `string` *required* The name of the file to be uploaded, including the file extension **content\_type** `string` *required* The MIME type of the file (e.g., "application/pdf", "text/plain", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "image/png", "audio/mp3") ## Supported File Types Wordsmith supports a wide variety of file formats including: * **Documents**: PDF, DOC, DOCX, TXT, MD, HTML * **Spreadsheets**: XLS, XLSX, CSV, TSV * **Presentations**: PPT, PPTX * **Images**: PNG, JPEG, WebP, TIFF, HEIC, HEIF * **Audio**: MP3, MP4, M4A, MPEG, WAV, WebM * **Archives**: ZIP ```bash cURL theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/files/upload-url" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "file_name": "contract.pdf", "content_type": "application/pdf" }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.wordsmith.ai/api/v1/files/upload-url", { method: "POST", headers: { Authorization: "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json", }, body: JSON.stringify({ file_name: "contract.pdf", content_type: "application/pdf", }), } ); const uploadInfo = await response.json(); ``` ```python Python theme={null} import requests headers = { "Authorization": "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json" } data = { "file_name": "contract.pdf", "content_type": "application/pdf" } response = requests.post( "https://api.wordsmith.ai/api/v1/files/upload-url", headers=headers, json=data ) upload_info = response.json() ``` ## Response **upload\_url** `string` The presigned URL where you can upload your file using a PUT request. **job\_id** `string` The unique identifier for this upload job. After you upload the file, pass it as `upload_job_id` in an agent question's `attachments`, or as `upload_job_id` in `attachments` when you [attach files to a task](/api-reference/tasks/attach-files) or add a task comment or thread reply. Each `job_id` can be used once. **content\_type** `string` The MIME type that was specified in the request. ```json Response theme={null} { "upload_url": "https://s3.amazonaws.com/wordsmith-uploads/signed-url-here", "job_id": "123e4567-e89b-12d3-a456-426614174000", "content_type": "application/pdf" } ``` ## Complete Upload Flow After receiving the upload URL, you need to upload your file: ```bash cURL theme={null} # Step 1: Get upload URL (shown above) # Step 2: Upload the file curl -X PUT "https://s3.amazonaws.com/wordsmith-uploads/signed-url-here" \ -H "Content-Type: application/pdf" \ --data-binary @contract.pdf # Step 3: Use upload_job_id in your agent question curl -X POST "https://api.wordsmith.ai/api/v1/agents/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "Please review this contract", "attachments": [ { "upload_job_id": "123e4567-e89b-12d3-a456-426614174000" } ] }' ``` ```javascript JavaScript theme={null} // Step 1: Get upload URL const uploadResponse = await fetch( "https://api.wordsmith.ai/api/v1/files/upload-url", { method: "POST", headers: { Authorization: "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json", }, body: JSON.stringify({ file_name: "contract.pdf", content_type: "application/pdf", }), } ); const { upload_url, job_id } = await uploadResponse.json(); // Step 2: Upload the file const fileUploadResponse = await fetch(upload_url, { method: "PUT", headers: { "Content-Type": "application/pdf", }, body: fileBlob, }); // Step 3: Use in agent question const questionResponse = await fetch( "https://api.wordsmith.ai/api/v1/agents/default/questions", { method: "POST", headers: { Authorization: "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json", }, body: JSON.stringify({ question: "Please review this contract", attachments: [{ upload_job_id: job_id }], }), } ); ``` ## Error Responses See the [Errors](/errors) page for the full error format reference. ```json 400 Bad Request - Invalid Body theme={null} { "error_code": "invalid_request_body", "ws_api_error_code": "invalid_request_body", "message": "Request body is invalid. Ensure it is valid JSON with 'file_name' and 'content_type' fields." } ``` ```json 401 Unauthorized theme={null} { "error_code": "unauthorized", "ws_api_error_code": "unauthorized", "message": "Invalid API key" } ``` ## File Size Limits * Maximum file size: 1 GB per file. A larger file is refused with `upload_too_large` when it is attached * Maximum total attachments per question: 10 files * Files are automatically deleted after 30 days for security ## Notes * Upload URLs expire after 1 hour * Files must be uploaded using a PUT request with the exact content type specified * Pass the `job_id` as `upload_job_id` in `attachments`, on agent questions and on the task file, comment and thread reply endpoints * Each `job_id` can be used once. Using it creates the file; the task response lists the new file's `id` # List Playbooks Source: https://wordsmithai.mintlify.app/api-reference/playbooks/list-playbooks GET /api/v1/playbooks Get all playbooks (document review workflows) that you have read access to Get a list of all playbooks available to your account. Playbooks are structured document review workflows that define specific checks and processes for analyzing legal documents. ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/playbooks' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/playbooks", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const playbooks = await response.json(); console.log(playbooks); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/playbooks" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } response = requests.get(url, headers=headers) playbooks = response.json() print(playbooks) ``` ```json Response theme={null} [ { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Contract Review Checklist", "description": "Comprehensive contract review workflow covering key terms, compliance, and risk factors", "url": "https://app.wordsmith.ai/playbooks/123e4567-e89b-12d3-a456-426614174000" }, { "id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "name": "Due Diligence Review", "description": "Structured review process for due diligence documents in M&A transactions", "url": "https://app.wordsmith.ai/playbooks/987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f" }, { "id": "abcdef12-3456-7890-abcd-ef1234567890", "name": "Employment Agreement Analysis", "description": null, "url": "https://app.wordsmith.ai/playbooks/abcdef12-3456-7890-abcd-ef1234567890" } ] ``` ## Response Fields **id** `string` The unique identifier for the playbook **name** `string` The display name of the playbook **description** `string | null` A description of the playbook's purpose and what it reviews. Can be null if no description is provided. **url** `string` Opens the playbook in Wordsmith. ## Notes * Only playbooks that you have read access to will be returned * The list includes both your organization's custom playbooks and any global playbooks you have access to * Playbooks define structured workflows for document review and analysis * Each playbook contains specific checks and processes tailored to different types of legal documents # Add Comment Source: https://wordsmithai.mintlify.app/api-reference/tasks/add-comment POST /api/v1/tasks/{task_id}/comments Add an internal comment to a task. Attach existing files with `file_ids`, and new uploads or public URLs with `attachments`. ## Body parameters **content** `string` *required* Comment body. **file\_ids** `array` Ids of files already in Wordsmith to attach. The comment stores a copy of each file in the task, so its `file_ids` hold new ids. The original file stays where it was. A `job_id` does not go here; use `attachments`. **attachments** `array` Up to 10 new files to attach, each with exactly one of `upload_job_id` (the `job_id` from the [file upload endpoint](/api-reference/files/upload-url), once the file is uploaded) or `url` (a public http(s) URL). See [Attach Files](/api-reference/tasks/attach-files) for the limits. ```bash cURL theme={null} curl --location --request POST 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "content": "Indemnity looks acceptable now." }' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ "content": "Indemnity looks acceptable now." }), }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } payload = { "content": "Indemnity looks acceptable now." } response = requests.post(url, headers=headers, json=payload) print(response.json()) ``` ```json Response theme={null} { "id": "aaaa1111-bbbb-2222-cccc-333333333333", "task_id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "content": "Indemnity looks acceptable now.", "author": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "parent_comment_id": null, "mentioned_users": [], "file_ids": [], "created_at": "2025-12-31T01:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ``` # Link Items Source: https://wordsmithai.mintlify.app/api-reference/tasks/add-links POST /api/v1/tasks/{task_id}/links Link chats, reviews, reports or other tasks to a task. Links each item to the task. An item the task already links is left unchanged; use [Update Link](/api-reference/tasks/update-link) to change its role or context. Returns the updated task. Each item takes these fields. Ids are prefixless, as everywhere in this API. | Field | Required | Description | | - | - | - | | `type` | yes | `chat`, `review`, `report` or `task`. | | `id` | yes | The linked item's UUID. For `type=task`, a display id like `#123` also works. | | `role` | no | Why the task links to it: `reference` (supporting context, the default), `subject` (what the task is about) or `output` (produced by the task). | | `context` | no | Fields specific to the link type. Only `review` links take one: `{"check_ids": [...]}` narrows the link to those checks of the review. | An item you cannot read is a `400`, the same as an item that does not exist. `role` and `context` added 2026-10-13. Each item must exist and be one you can open. Everyone on the task sees a link's title, so a missing or unreadable item is refused with a `400`, and nothing is linked. ## Body parameters **linked\_items** `array` *required* The items to link. At least one. ```bash cURL theme={null} curl --location --request POST 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/links' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "linked_items": [ { "type": "chat", "id": "5f6a7b8c-9d0e-1f20-3a4b-5c6d7e8f9012" }, { "type": "review", "id": "8e1d2c3b-4a59-4687-9a0b-1c2d3e4f5a6b", "role": "subject", "context": { "check_ids": ["liability-cap"] } } ] }' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/links", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ "linked_items": [ { "type": "chat", "id": "5f6a7b8c-9d0e-1f20-3a4b-5c6d7e8f9012" }, { "type": "review", "id": "8e1d2c3b-4a59-4687-9a0b-1c2d3e4f5a6b", "role": "subject", "context": { "check_ids": ["liability-cap"] } } ] }), }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/links" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } payload = { "linked_items": [ { "type": "chat", "id": "5f6a7b8c-9d0e-1f20-3a4b-5c6d7e8f9012" }, { "type": "review", "id": "8e1d2c3b-4a59-4687-9a0b-1c2d3e4f5a6b", "role": "subject", "context": {"check_ids": ["liability-cap"]} } ] } response = requests.post(url, headers=headers, json=payload) print(response.json()) ``` ```json Response theme={null} { "id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "display_id": "#12", "task_number": 12, "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "additional_context": "Counterparty has pushed back twice on liability caps.", "status": "in_progress", "priority": "high", "team": { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal" }, "creator": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_to": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_agent": null, "assigned_team": null, "matter": null, "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ], "source_channel": "web", "visibility": "customer", "archived": false, "draft": false, "involved_people": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "role": "assignee", "access_level": "write" } ], "shared_teams": [], "linked_items": [ { "type": "chat", "id": "5f6a7b8c-9d0e-1f20-3a4b-5c6d7e8f9012", "display_name": "NDA negotiation notes", "role": "reference", "context": null, "is_output": false }, { "type": "review", "id": "8e1d2c3b-4a59-4687-9a0b-1c2d3e4f5a6b", "display_name": "Acme NDA", "role": "subject", "context": { "check_ids": [ "liability-cap" ] }, "is_output": false } ], "input_files": [], "output_files": [], "url": "https://app.wordsmith.ai/tasks?activeId=9f8e7d6c-5b4a-3210-9876-543210fedcba", "created_at": "2025-12-31T00:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ``` # Reply to Requester Source: https://wordsmithai.mintlify.app/api-reference/tasks/add-thread-reply POST /api/v1/tasks/{task_id}/thread Send a reply back to whoever requested the task. This reaches the requester over the task's origin channel. For a connected personal mailbox the reply is staged as a draft for the mailbox owner to review and send, rather than sent immediately. The `delivery_status` on the returned message says which happened — `sent` or `draft` — so do not report a reply as delivered without reading it. For an internal note that the requester never sees, add a [comment](/api-reference/tasks/add-comment) instead. > [!NOTE] > > The requester thread is behind its own feature flag, separate from tasks. This endpoint returns `404` unless it is enabled for your organization. A task that did not arrive over Slack, email or Teams has no requester to reply to. This endpoint returns `400` for such a task, and any `job_id` in `attachments` stays usable. ## Partly delivered replies A reply can reach the requester even though Wordsmith could not confirm that everything completed, for example an attachment that could not be delivered. The response is then a `400` with `error_code` `task_thread_reply_partially_delivered`: ```json theme={null} { "error_code": "task_thread_reply_partially_delivered", "ws_api_error_code": "task_thread_reply_partially_delivered", "message": "Your reply was sent, but we couldn’t confirm everything completed. Check the requester thread before sending a follow-up." } ``` Do not retry this request. The requester already has the reply, and sending it again delivers it twice. The [thread](/api-reference/tasks/list-thread) may not show the reply, because recording it is one of the steps that can fail. Check the conversation in the origin channel (Slack, Teams or email) before you send a follow-up. Any other `400` means nothing was sent, so you can fix the request and retry. ## Body parameters **content** `string` *required* The reply body, sent to the requester. **file\_ids** `array` Ids of files already in Wordsmith to attach. A `job_id` does not go here; use `attachments`. **attachments** `array` Up to 10 new files to attach, each with exactly one of `upload_job_id` (the `job_id` from the [file upload endpoint](/api-reference/files/upload-url), once the file is uploaded) or `url` (a public http(s) URL). See [Attach Files](/api-reference/tasks/attach-files) for the limits. ```bash cURL theme={null} curl --location --request POST 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/thread' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "content": "We've reviewed the NDA \u2014 see the attached mark-up." }' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/thread", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ "content": "We've reviewed the NDA \u2014 see the attached mark-up." }), }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/thread" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } payload = { "content": "We've reviewed the NDA \u2014 see the attached mark-up." } response = requests.post(url, headers=headers, json=payload) print(response.json()) ``` ```json Response theme={null} { "id": "bbbb1111-cccc-2222-dddd-444444444444", "task_id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "direction": "outbound", "content": "We've reviewed the NDA \u2014 see the attached mark-up.", "file_ids": [], "sender": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "sender_agent": null, "sender_display_name": "Jane Doe", "delivery_status": "sent", "created_at": "2025-12-31T01:01:40Z" } ``` # Attach Files Source: https://wordsmithai.mintlify.app/api-reference/tasks/attach-files POST /api/v1/tasks/{task_id}/files Attach uploaded files to a task. Attach a new file in three steps: 1. Call the [file upload endpoint](/api-reference/files/upload-url). It returns a `job_id` and an `upload_url`. 2. `PUT` the file to `upload_url` with the same `Content-Type`. 3. Call this endpoint with `{"upload_job_id": ""}` in `attachments`. A file at a public URL needs no upload: pass `{"url": "https://..."}` in `attachments` and Wordsmith downloads it. To attach a file that is already in Wordsmith, for example from another task's `input_files`, pass its `id` in `file_ids`. Returns the updated task. Each new file appears in `input_files` with its own file `id`, which differs from the `job_id`. The task stores a copy of each file, so the file `id` in `input_files` differs from the one you passed in `file_ids`. The original file stays where it was, and the people who could open it still can. A file that is already on this task keeps its `id`. A `job_id` expires after 1 hour and can be used once. A job used here cannot also be sent to an agent question, and the other way round. A rejected request uses up no `job_id`, so you can fix the request and retry. ## Body parameters Pass at least one file, in `file_ids` or `attachments`. **file\_ids** `array` Ids of files already in Wordsmith. The task gets a copy of each one, with a new file `id`. A `job_id` does not go here; use `attachments`. **attachments** `array` Up to 10 new files. Each entry has exactly one of: * `upload_job_id`: the `job_id` from the [file upload endpoint](/api-reference/files/upload-url), once the file is uploaded. * `url`: a public http(s) URL. It must return the file with status 200 within 20 seconds, and the file must be under 256 MB. ```bash cURL theme={null} curl --location --request POST 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/files' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "attachments": [ { "upload_job_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } ] }' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/files", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ "attachments": [ { "upload_job_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } ] }), }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/files" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } payload = { "attachments": [ {"upload_job_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"} ] } response = requests.post(url, headers=headers, json=payload) print(response.json()) ``` ```json Response theme={null} { "id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "display_id": "#12", "task_number": 12, "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "additional_context": "Counterparty has pushed back twice on liability caps.", "status": "in_progress", "priority": "high", "team": { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal" }, "creator": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_to": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_agent": null, "assigned_team": null, "matter": null, "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ], "source_channel": "web", "visibility": "customer", "archived": false, "draft": false, "involved_people": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "role": "assignee", "access_level": "write" } ], "shared_teams": [], "linked_items": [], "input_files": [], "output_files": [], "url": "https://app.wordsmith.ai/tasks?activeId=9f8e7d6c-5b4a-3210-9876-543210fedcba", "created_at": "2025-12-31T00:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ``` # Create Task Source: https://wordsmithai.mintlify.app/api-reference/tasks/create-task POST /api/v1/tasks Create a task, optionally assigned to a person, an agent or a team. `assigned_to` and `assigned_agent` are independent — a task can carry both a human owner and an executing agent. Set `draft: true` to stage a task for review; accept it later by patching `draft` to `false`. People, agents and teams can be referenced by id or by name: a person by user id, email address or name; an agent by its id, name or shortcut; a team by id or name. A reference that matches more than one record is rejected with a `400` that lists the candidates, so pass the id to disambiguate. In the response, `assigned_agent.id` is the agent's record id and `assigned_agent.counsel_id` is the agent id [List Agents](/api-reference/agents/list-agents) returns; either is accepted wherever an agent is referenced. `counsel_id` added 2026-09-05. Tasks are created in your organization's default task team. Choosing a different team, and creating a task as private, are not supported in v1. ## Body parameters **title** `string` *required* Short summary of the work to be done. **description** `string` The primary instruction for whoever picks the task up. **parent\_task\_id** `string (uuid)` Create this task as a subtask of another task. **assigned\_to** `string` Person responsible for the task: a user id, email address, or name. **assigned\_agent** `string` Agent that should execute the task: an agent id, the agent's name, or its shortcut. Independent of assigned\_to — a task may have both. **assigned\_team\_id** `string` Team the task is assigned to: a team id or team name. Grants that team editor access. **matter\_id** `string (uuid)` Matter to file the task under. **status** `enum` Initial status. Defaults to the team's first status. **priority** `enum` Task priority. **complete\_by** `string` Due date as an ISO 8601 timestamp, e.g. "2026-03-22T17:00:00Z". **tags** `array` Free-form labels. **linked\_items** `array` Chats, reviews, reports or other tasks to link. Each item takes `type`, `id`, and optionally `role` and `context`, as in [Link Items](/api-reference/tasks/add-links). **draft** `boolean` Create as a draft rather than a committed work item. Accept later with a PATCH of draft=false. ```bash cURL theme={null} curl --location --request POST 'https://api.wordsmith.ai/api/v1/tasks' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "priority": "high", "assigned_to": "11111111-2222-3333-4444-555555555555", "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ] }' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "priority": "high", "assigned_to": "11111111-2222-3333-4444-555555555555", "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ] }), }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } payload = { "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "priority": "high", "assigned_to": "11111111-2222-3333-4444-555555555555", "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ] } response = requests.post(url, headers=headers, json=payload) print(response.json()) ``` ```json Response theme={null} { "id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "display_id": "#12", "task_number": 12, "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "additional_context": "Counterparty has pushed back twice on liability caps.", "status": "in_progress", "priority": "high", "team": { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal" }, "creator": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_to": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_agent": { "id": "66666666-6666-4666-8666-666666666666", "counsel_id": "77777777-7777-4777-8777-777777777777", "name": "Paralegal" }, "assigned_team": null, "matter": null, "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ], "source_channel": "web", "visibility": "customer", "archived": false, "draft": false, "involved_people": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "role": "assignee", "access_level": "write" } ], "shared_teams": [], "linked_items": [], "input_files": [], "output_files": [], "url": "https://app.wordsmith.ai/tasks?activeId=9f8e7d6c-5b4a-3210-9876-543210fedcba", "created_at": "2025-12-31T00:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ``` # Delete Comment Source: https://wordsmithai.mintlify.app/api-reference/tasks/delete-comment DELETE /api/v1/tasks/{task_id}/comments/{comment_id} Delete a comment. Returns `204 No Content` on success. ```bash cURL theme={null} curl --location --request DELETE 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments/aaaa1111-bbbb-2222-cccc-333333333333' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments/aaaa1111-bbbb-2222-cccc-333333333333", { method: "DELETE", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments/aaaa1111-bbbb-2222-cccc-333333333333" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.delete(url, headers=headers) print(response.status_code) ``` ```text Response theme={null} 204 No Content ``` # Delete Task Source: https://wordsmithai.mintlify.app/api-reference/tasks/delete-task DELETE /api/v1/tasks/{task_id} Delete a task, or reject a draft. Deleting a task also removes its subtasks. Rejecting a draft is the same operation — delete the draft task. Returns `204 No Content` on success. ```bash cURL theme={null} curl --location --request DELETE 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba", { method: "DELETE", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.delete(url, headers=headers) print(response.status_code) ``` ```text Response theme={null} 204 No Content ``` # Follow Task Source: https://wordsmithai.mintlify.app/api-reference/tasks/follow-task PUT /api/v1/tasks/{task_id}/follow Follow a task to receive its notifications. Idempotent — following an already-followed task succeeds. ```bash cURL theme={null} curl --location --request PUT 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/follow' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/follow", { method: "PUT", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/follow" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.put(url, headers=headers) print(response.json()) ``` ```json Response theme={null} { "task_id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "following": true } ``` # Get Follow Status Source: https://wordsmithai.mintlify.app/api-reference/tasks/get-follow GET /api/v1/tasks/{task_id}/follow Check whether you are following a task. Followers receive notifications when the task changes. ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/follow' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/follow", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/follow" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.get(url, headers=headers) print(response.json()) ``` ```json Response theme={null} { "task_id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "following": true } ``` # Get Task Sharing Source: https://wordsmithai.mintlify.app/api-reference/tasks/get-sharing GET /api/v1/tasks/{task_id}/sharing Read who a task is shared with. `visibility` is `private` (only the people and teams listed) or `customer` (anyone in your organization). ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/sharing' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/sharing", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/sharing" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.get(url, headers=headers) print(response.json()) ``` ```json Response theme={null} { "visibility": "private", "users": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "role": "assignee", "access_level": "write" } ], "teams": [ { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal", "access_level": "read" } ] } ``` # Get Task Source: https://wordsmithai.mintlify.app/api-reference/tasks/get-task GET /api/v1/tasks/{task_id} Fetch a single task by id. `display_id` is the task's human-facing key, as shown in the Wordsmith UI. When an agent is assigned, `assigned_agent.id` is the agent's record id and `assigned_agent.counsel_id` is the agent id [List Agents](/api-reference/agents/list-agents) returns; either is accepted wherever an agent is referenced. `counsel_id` added 2026-09-05. `url` opens the task in Wordsmith. Viewers must sign in. `url` added 2026-10-16. Each entry in `linked_items` is a chat, review, report or task the task links to: * `type` and `id` name the item. `display_name` is its name when it was linked. * `role` says why the task links to it: `reference` (supporting context), `subject` (what the task is about) or `output` (produced by the task). * `context` holds fields specific to the link type, or null. A `review` link can carry `{"check_ids": [...]}`, the checks of the review it is about. * `is_output` is `true` when `role` is `output`. It is kept for clients written before `role`. `role` and `context` added 2026-10-13. ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.get(url, headers=headers) print(response.json()) ``` ```json Response theme={null} { "id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "display_id": "#12", "task_number": 12, "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "additional_context": "Counterparty has pushed back twice on liability caps.", "status": "in_progress", "priority": "high", "team": { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal" }, "creator": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_to": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_agent": null, "assigned_team": null, "matter": null, "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ], "source_channel": "web", "visibility": "customer", "archived": false, "draft": false, "involved_people": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "role": "assignee", "access_level": "write" } ], "shared_teams": [], "linked_items": [ { "type": "review", "id": "8e1d2c3b-4a59-4687-9a0b-1c2d3e4f5a6b", "display_name": "Acme NDA", "role": "subject", "context": { "check_ids": [ "liability-cap" ] }, "is_output": false } ], "input_files": [], "output_files": [], "url": "https://app.wordsmith.ai/tasks?activeId=9f8e7d6c-5b4a-3210-9876-543210fedcba", "created_at": "2025-12-31T00:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ``` # List Comments Source: https://wordsmithai.mintlify.app/api-reference/tasks/list-comments GET /api/v1/tasks/{task_id}/comments List the internal comments on a task. Comments are the task's internal discussion, visible to people with access to the task. To reply to whoever requested the task instead, use the [thread endpoint](/api-reference/tasks/add-thread-reply). ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.get(url, headers=headers) print(response.json()) ``` ```json Response theme={null} [ { "id": "aaaa1111-bbbb-2222-cccc-333333333333", "task_id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "content": "Indemnity looks acceptable now.", "author": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "parent_comment_id": null, "mentioned_users": [], "file_ids": [], "created_at": "2025-12-31T01:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ] ``` # List Subtasks Source: https://wordsmithai.mintlify.app/api-reference/tasks/list-subtasks GET /api/v1/tasks/{task_id}/subtasks List the subtasks of a task. Create a subtask by passing `parent_task_id` when creating a task. ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/subtasks' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/subtasks", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/subtasks" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.get(url, headers=headers) print(response.json()) ``` ```json Response theme={null} [ { "id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "display_id": "#12", "task_number": 12, "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "additional_context": "Counterparty has pushed back twice on liability caps.", "status": "in_progress", "priority": "high", "team": { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal" }, "creator": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_to": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_agent": null, "assigned_team": null, "matter": null, "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ], "source_channel": "web", "visibility": "customer", "archived": false, "draft": false, "involved_people": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "role": "assignee", "access_level": "write" } ], "shared_teams": [], "linked_items": [], "input_files": [], "output_files": [], "url": "https://app.wordsmith.ai/tasks?activeId=9f8e7d6c-5b4a-3210-9876-543210fedcba", "created_at": "2025-12-31T00:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ] ``` # List Tasks Source: https://wordsmithai.mintlify.app/api-reference/tasks/list-tasks GET /api/v1/tasks List and filter tasks you have access to. Returns a cursor-paginated page of tasks, ordered by priority, then due date, then most recently created. Filtering by `status=completed` alone is the exception: finished work comes back most recently completed first, since priority order there buries the latest completions. Results are scoped to what your API key's user can see. Pass the `cursor` from a response to fetch the next page; a null `cursor` means you have reached the end. The cursor marks the row where the next page starts, so you can change `limit` between pages. Archived and draft tasks are excluded unless you filter for them. `assigned_to` and `involving` take a user id, email address or name. A user id is applied as given, so the tasks of a deactivated user stay listable. An email or name that matches nobody, or more than one person, is a `400` rather than an empty page; the ambiguous case lists the candidates. When an item has an agent assigned, `assigned_agent.id` is the agent's record id and `assigned_agent.counsel_id` is the agent id [List Agents](/api-reference/agents/list-agents) returns; either is accepted wherever an agent is referenced. `counsel_id` added 2026-09-05. `linked_type` and `linked_id` find the tasks that link to an item, such as every task about a review. Added 2026-10-13. ## Query parameters **query** `string` Free-text search across title, description, additional context and comment text. **status** `string` Comma-separated: `not_started`, `in_progress`, `waiting_on_external`, `completed`. **priority** `string` One of `no_priority`, `urgent`, `high`, `medium`, `low`. **assigned\_to** `string` A user id, email address, or name — only tasks assigned to this person. **involving** `string` A user id, email address, or name — tasks this person is involved in at all (assignee, creator or shared). **team\_id** `string` Task team id. **tags** `string` Comma-separated tag list. **source\_channel** `string` Comma-separated source groups — only tasks that came in through any of them: `email` (forwarding address and connected mailboxes), `slack`, `teams`, `whatsapp`, `web_app` (web, iOS, Android, Office add-ins, template drafting, report and playbook chats), `api`, `scheduled`, `agent` (created by an agent run, RSS or an evaluation). An unknown value is a `400`. Each task's raw channel is returned in `source_channel`. **complete\_by\_from** `string` ISO 8601 timestamp, e.g. `2026-01-01` or `2026-01-01T09:00:00Z` — only tasks due at or after it. **complete\_by\_to** `string` ISO 8601 timestamp — only tasks due at or before it. **linked\_type** `string` One of `chat`, `review`, `report` or `task` — with `linked_id`, only tasks that link to that item. Send both or neither. **linked\_id** `string` The linked item's UUID. For `linked_type=task`, a display id like `#123` also works. **cursor** `string` Pagination cursor from a previous response. **limit** `string` Page size, 1–100. Defaults to 20. Values below 1 are rejected. ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/tasks' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.get(url, headers=headers) print(response.json()) ``` ```json Response theme={null} { "items": [ { "id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "display_id": "#12", "task_number": 12, "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "additional_context": "Counterparty has pushed back twice on liability caps.", "status": "in_progress", "priority": "high", "team": { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal" }, "creator": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_to": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_agent": null, "assigned_team": null, "matter": null, "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ], "source_channel": "web", "visibility": "customer", "archived": false, "draft": false, "involved_people": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "role": "assignee", "access_level": "write" } ], "shared_teams": [], "linked_items": [], "input_files": [], "output_files": [], "url": "https://app.wordsmith.ai/tasks?activeId=9f8e7d6c-5b4a-3210-9876-543210fedcba", "created_at": "2025-12-31T00:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ], "cursor": "20" } ``` # List Thread Messages Source: https://wordsmithai.mintlify.app/api-reference/tasks/list-thread GET /api/v1/tasks/{task_id}/thread Read the conversation with whoever requested the task. Tasks raised over Slack, email or a mailbox carry a thread with the requester. `direction` is `inbound` for messages from the requester and `outbound` for replies back to them. A reply written by an agent sets `sender_agent`: its `id` is the agent's record id and its `counsel_id` is the agent id [List Agents](/api-reference/agents/list-agents) returns. `counsel_id` added 2026-09-05. `direction` does not tell you whether an outbound reply reached the requester — read `delivery_status` for that. It is `sent` once the channel confirmed delivery, and `draft` while the reply is only staged in a connected mailbox for its owner to review and send. `failed` and `canceled` are reserved for a reply that was attempted and did not arrive. Treat anything other than `sent` as not delivered, including a value your integration does not recognise. Inbound messages are always `sent`. > [!NOTE] > > The requester thread is behind its own feature flag, separate from tasks. These two endpoints return `404` unless it is enabled for your organization. ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/thread' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/thread", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/thread" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.get(url, headers=headers) print(response.json()) ``` ```json Response theme={null} [ { "id": "bbbb1111-cccc-2222-dddd-444444444444", "task_id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "direction": "outbound", "content": "We've reviewed the NDA \u2014 see the attached mark-up.", "file_ids": [], "sender": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "sender_agent": null, "sender_display_name": "Jane Doe", "delivery_status": "sent", "created_at": "2025-12-31T01:01:40Z" }, { "id": "cccc2222-dddd-3333-eeee-555555555555", "task_id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "direction": "outbound", "content": "The revised termination clause is attached.", "file_ids": [], "sender": null, "sender_agent": { "id": "66666666-6666-4666-8666-666666666666", "counsel_id": "77777777-7777-4777-8777-777777777777", "name": "Paralegal" }, "sender_display_name": "Paralegal", "delivery_status": "draft", "created_at": "2025-12-31T02:15:00Z" } ] ``` # Remove File Source: https://wordsmithai.mintlify.app/api-reference/tasks/remove-file DELETE /api/v1/tasks/{task_id}/files/{file_id} Detach one file from a task. Detaches the file from the task and returns the updated task. The file is addressed in the path rather than a request body, since bodies on `DELETE` are unreliable through proxies. Pass an `id` from the task's `input_files` or `output_files`. Attaching stores a copy with a new id, so the id you attached is not on the task. An id the task does not list is a `404`. Detaching deletes the task's copy, unless a comment or a thread message still shows it. The original file you attached is not touched. ```bash cURL theme={null} curl --location --request DELETE 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/files/7c9e6679-7425-40de-944b-e07fc1f90ae7' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/files/7c9e6679-7425-40de-944b-e07fc1f90ae7", { method: "DELETE", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/files/7c9e6679-7425-40de-944b-e07fc1f90ae7" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.delete(url, headers=headers) print(response.json()) ``` ```json Response theme={null} { "id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "display_id": "#12", "task_number": 12, "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "additional_context": "Counterparty has pushed back twice on liability caps.", "status": "in_progress", "priority": "high", "team": { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal" }, "creator": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_to": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_agent": null, "assigned_team": null, "matter": null, "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ], "source_channel": "web", "visibility": "customer", "archived": false, "draft": false, "involved_people": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "role": "assignee", "access_level": "write" } ], "shared_teams": [], "linked_items": [], "input_files": [], "output_files": [], "url": "https://app.wordsmith.ai/tasks?activeId=9f8e7d6c-5b4a-3210-9876-543210fedcba", "created_at": "2025-12-31T00:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ``` # Unlink Item Source: https://wordsmithai.mintlify.app/api-reference/tasks/remove-link DELETE /api/v1/tasks/{task_id}/links/{link_type}/{item_id} Remove one link from a task. The link is addressed by type and id in the path — `DELETE /links/chat/` — rather than a request body. Returns the updated task. To change a link's role or context instead, use [Update Link](/api-reference/tasks/update-link). ```bash cURL theme={null} curl --location --request DELETE 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/links/chat/5f6a7b8c-9d0e-1f20-3a4b-5c6d7e8f9012' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/links/chat/5f6a7b8c-9d0e-1f20-3a4b-5c6d7e8f9012", { method: "DELETE", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/links/chat/5f6a7b8c-9d0e-1f20-3a4b-5c6d7e8f9012" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.delete(url, headers=headers) print(response.json()) ``` ```json Response theme={null} { "id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "display_id": "#12", "task_number": 12, "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "additional_context": "Counterparty has pushed back twice on liability caps.", "status": "in_progress", "priority": "high", "team": { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal" }, "creator": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_to": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_agent": null, "assigned_team": null, "matter": null, "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ], "source_channel": "web", "visibility": "customer", "archived": false, "draft": false, "involved_people": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "role": "assignee", "access_level": "write" } ], "shared_teams": [], "linked_items": [], "input_files": [], "output_files": [], "url": "https://app.wordsmith.ai/tasks?activeId=9f8e7d6c-5b4a-3210-9876-543210fedcba", "created_at": "2025-12-31T00:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ``` # Reply to Comment Source: https://wordsmithai.mintlify.app/api-reference/tasks/reply-comment POST /api/v1/tasks/{task_id}/comments/{comment_id}/replies Reply to a comment, creating a threaded discussion. The reply's `parent_comment_id` is set to the comment you replied to. ## Body parameters **content** `string` *required* Comment body. **file\_ids** `array` Ids of files already in Wordsmith to attach. The comment stores a copy of each file in the task, so its `file_ids` hold new ids. The original file stays where it was. A `job_id` does not go here; use `attachments`. **attachments** `array` Up to 10 new files to attach, each with exactly one of `upload_job_id` (the `job_id` from the [file upload endpoint](/api-reference/files/upload-url), once the file is uploaded) or `url` (a public http(s) URL). See [Attach Files](/api-reference/tasks/attach-files) for the limits. ```bash cURL theme={null} curl --location --request POST 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments/aaaa1111-bbbb-2222-cccc-333333333333/replies' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "content": "Agreed \u2014 approving." }' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments/aaaa1111-bbbb-2222-cccc-333333333333/replies", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ "content": "Agreed \u2014 approving." }), }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments/aaaa1111-bbbb-2222-cccc-333333333333/replies" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } payload = { "content": "Agreed \u2014 approving." } response = requests.post(url, headers=headers, json=payload) print(response.json()) ``` ```json Response theme={null} { "id": "cccc1111-dddd-2222-eeee-555555555555", "task_id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "content": "Agreed \u2014 approving.", "author": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "parent_comment_id": "aaaa1111-bbbb-2222-cccc-333333333333", "mentioned_users": [], "file_ids": [], "created_at": "2025-12-31T01:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ``` # Unfollow Task Source: https://wordsmithai.mintlify.app/api-reference/tasks/unfollow-task DELETE /api/v1/tasks/{task_id}/follow Stop following a task. Idempotent — unfollowing a task you don't follow succeeds. ```bash cURL theme={null} curl --location --request DELETE 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/follow' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/follow", { method: "DELETE", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/follow" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } response = requests.delete(url, headers=headers) print(response.json()) ``` ```json Response theme={null} { "task_id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "following": false } ``` # Update Comment Source: https://wordsmithai.mintlify.app/api-reference/tasks/update-comment PATCH /api/v1/tasks/{task_id}/comments/{comment_id} Edit the content of a comment you authored. Edit the content of a comment you authored. ## Body parameters **content** `string` *required* The replacement comment body. ```bash cURL theme={null} curl --location --request PATCH 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments/aaaa1111-bbbb-2222-cccc-333333333333' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "content": "Indemnity is acceptable after the cap change." }' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments/aaaa1111-bbbb-2222-cccc-333333333333", { method: "PATCH", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ "content": "Indemnity is acceptable after the cap change." }), }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/comments/aaaa1111-bbbb-2222-cccc-333333333333" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } payload = { "content": "Indemnity is acceptable after the cap change." } response = requests.patch(url, headers=headers, json=payload) print(response.json()) ``` ```json Response theme={null} { "id": "aaaa1111-bbbb-2222-cccc-333333333333", "task_id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "content": "Indemnity is acceptable after the cap change.", "author": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "parent_comment_id": null, "mentioned_users": [], "file_ids": [], "created_at": "2025-12-31T01:00:00Z", "updated_at": "2025-12-31T01:03:20Z" } ``` # Update Link Source: https://wordsmithai.mintlify.app/api-reference/tasks/update-link PATCH /api/v1/tasks/{task_id}/links/{link_type}/{item_id} Change the role or context of one link on a task. The link is addressed by type and id in the path, as in [Unlink Item](/api-reference/tasks/remove-link). Only the fields you send are changed, and at least one is required. Returns the updated task. A link the task does not have is a `404`. A `context` that does not fit the link type is a `400`. Added 2026-10-13. ## Body parameters **role** `enum` Why the task links to the item: `reference` (supporting context), `subject` (what the task is about) or `output` (produced by the task). **context** `object` Fields specific to the link type. Only `review` links take one: `{"check_ids": [...]}` narrows the link to those checks of the review. Send null to clear it. ```bash cURL theme={null} curl --location --request PATCH 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/links/review/8e1d2c3b-4a59-4687-9a0b-1c2d3e4f5a6b' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "role": "subject", "context": { "check_ids": ["liability-cap", "indemnity"] } }' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/links/review/8e1d2c3b-4a59-4687-9a0b-1c2d3e4f5a6b", { method: "PATCH", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ "role": "subject", "context": { "check_ids": ["liability-cap", "indemnity"] } }), }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/links/review/8e1d2c3b-4a59-4687-9a0b-1c2d3e4f5a6b" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } payload = { "role": "subject", "context": {"check_ids": ["liability-cap", "indemnity"]} } response = requests.patch(url, headers=headers, json=payload) print(response.json()) ``` ```json Response theme={null} { "id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "display_id": "#12", "task_number": 12, "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "additional_context": "Counterparty has pushed back twice on liability caps.", "status": "in_progress", "priority": "high", "team": { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal" }, "creator": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_to": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_agent": null, "assigned_team": null, "matter": null, "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ], "source_channel": "web", "visibility": "customer", "archived": false, "draft": false, "involved_people": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "role": "assignee", "access_level": "write" } ], "shared_teams": [], "linked_items": [ { "type": "review", "id": "8e1d2c3b-4a59-4687-9a0b-1c2d3e4f5a6b", "display_name": "Acme NDA", "role": "subject", "context": { "check_ids": [ "liability-cap", "indemnity" ] }, "is_output": false } ], "input_files": [], "output_files": [], "url": "https://app.wordsmith.ai/tasks?activeId=9f8e7d6c-5b4a-3210-9876-543210fedcba", "created_at": "2025-12-31T00:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ``` # Update Task Sharing Source: https://wordsmithai.mintlify.app/api-reference/tasks/update-sharing PUT /api/v1/tasks/{task_id}/sharing Replace a task's visibility and its shared people and teams. This replaces the whole sharing state, so send the full desired lists — anyone omitted loses access. `access_level` is `read` or `write`. `user_id` takes a user id, email address or name; `team_id` takes a team id or team name. A reference that matches more than one record is rejected with a `400` that lists the candidates, so pass the id to disambiguate. ## Body parameters **visibility** `enum` *required* Who can see the task by default. **users** `array` The complete set of people with access. Anyone omitted loses access. Each entry is `{"user_id": "", "access_level": "read" | "write"}`. **teams** `array` The complete set of teams with access. Any team omitted loses access. Each entry is `{"team_id": "", "access_level": "read" | "write"}`. ```bash cURL theme={null} curl --location --request PUT 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/sharing' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "visibility": "private", "users": [ { "user_id": "11111111-2222-3333-4444-555555555555", "access_level": "write" } ], "teams": [ { "team_id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "access_level": "read" } ] }' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/sharing", { method: "PUT", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ "visibility": "private", "users": [ { "user_id": "11111111-2222-3333-4444-555555555555", "access_level": "write" } ], "teams": [ { "team_id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "access_level": "read" } ] }), }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba/sharing" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } payload = { "visibility": "private", "users": [ { "user_id": "11111111-2222-3333-4444-555555555555", "access_level": "write" } ], "teams": [ { "team_id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "access_level": "read" } ] } response = requests.put(url, headers=headers, json=payload) print(response.json()) ``` ```json Response theme={null} { "visibility": "private", "users": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "role": "assignee", "access_level": "write" } ], "teams": [ { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal", "access_level": "read" } ] } ``` # Update Task Source: https://wordsmithai.mintlify.app/api-reference/tasks/update-task PATCH /api/v1/tasks/{task_id} Update any task field, including archiving and accepting drafts. Only the fields you send are changed, and `{}` does nothing. An explicit `null` **clears** these fields: `description`, `additional_context`, `assigned_to`, `assigned_agent`, `assigned_team_id`, `complete_by`, `matter_id`. So `{"assigned_to": null}` unassigns the task. For every other field a `null` is ignored rather than applied — send a real value (an empty array to empty `tags` or `linked_items`). State changes are fields rather than separate endpoints: | To do this | Send | | - | - | | Archive | `{"archived": true}` | | Restore from archive | `{"archived": false}` | | Accept a draft | `{"draft": false}` | | Reassign | `{"assigned_to": ""}` | | Unassign | `{"assigned_to": null}` | Archiving and drafting are orthogonal to `status`: an archived task keeps the status it had. People, agents and teams can be referenced by id or by name: a person by user id, email address or name; an agent by its id, name or shortcut; a team by id or name. A reference that matches more than one record is rejected with a `400` that lists the candidates, so pass the id to disambiguate. In the response, `assigned_agent.id` is the agent's record id and `assigned_agent.counsel_id` is the agent id [List Agents](/api-reference/agents/list-agents) returns; either is accepted in `assigned_agent`. `counsel_id` added 2026-09-05. ## Body parameters **title** `string` Short summary of the work. **description** `string` The primary instruction. Send null to clear it. **additional\_context** `string` Supplementary context; the description wins on conflict. Send null to clear it. **assigned\_to** `string` A user id, email address, or name. Send null to unassign. **assigned\_agent** `string` An agent id, the agent's name, or its shortcut. Send null to detach the agent. **assigned\_team\_id** `string` A team id or team name. Send null to unassign the team. **matter\_id** `string (uuid)` Send null to unfile the task. **status** `enum` not\_started, in\_progress, waiting\_on\_external or completed. **priority** `enum` no\_priority, urgent, high, medium or low. **complete\_by** `string` ISO 8601, e.g. "2026-03-22T17:00:00Z". Send null to clear the due date. **tags** `array` Replaces the whole tag list. **linked\_items** `array` Replaces the whole link list. Each item takes `type`, `id`, and optionally `role` and `context`, as in [Link Items](/api-reference/tasks/add-links). A link the task already has keeps its role and context unless the item sends them. To change one link, use [Update Link](/api-reference/tasks/update-link). **involved\_people** `array` Replaces the set of people involved in the task. Each entry is a user id, email address, or name. **archived** `boolean` Shelve (true) or restore (false) the task. Orthogonal to status — the task keeps its status. **draft** `boolean` Accept a draft with false. Orthogonal to status. To reject a draft, DELETE the task. ```bash cURL theme={null} curl --location --request PATCH 'https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "status": "completed", "priority": "medium" }' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba", { method: "PATCH", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ "status": "completed", "priority": "medium" }), }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/tasks/9f8e7d6c-5b4a-3210-9876-543210fedcba" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", } payload = { "status": "completed", "priority": "medium" } response = requests.patch(url, headers=headers, json=payload) print(response.json()) ``` ```json Response theme={null} { "id": "9f8e7d6c-5b4a-3210-9876-543210fedcba", "display_id": "#12", "task_number": 12, "title": "Review the Acme NDA", "description": "Check the indemnity and termination clauses.", "additional_context": "Counterparty has pushed back twice on liability caps.", "status": "completed", "priority": "medium", "team": { "id": "1a2b3c4d-5e6f-7081-9200-aabbccddeeff", "name": "Legal" }, "creator": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_to": { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com" }, "assigned_agent": { "id": "66666666-6666-4666-8666-666666666666", "counsel_id": "77777777-7777-4777-8777-777777777777", "name": "Paralegal" }, "assigned_team": null, "matter": null, "complete_by": "2026-01-01T00:00:00Z", "tags": [ "nda", "urgent" ], "source_channel": "web", "visibility": "customer", "archived": false, "draft": false, "involved_people": [ { "id": "11111111-2222-3333-4444-555555555555", "name": "Jane Doe", "email": "jane@example.com", "role": "assignee", "access_level": "write" } ], "shared_teams": [], "linked_items": [], "input_files": [], "output_files": [], "url": "https://app.wordsmith.ai/tasks?activeId=9f8e7d6c-5b4a-3210-9876-543210fedcba", "created_at": "2025-12-31T00:00:00Z", "updated_at": "2025-12-31T01:00:00Z" } ``` # List Templates Source: https://wordsmithai.mintlify.app/api-reference/templates/list-templates GET /api/v1/templates Get all document templates that you have read access to Get a list of all document templates available to your account. Templates are reusable document structures with placeholders that can be filled in to generate customized legal documents. ```bash cURL theme={null} curl --location --request GET 'https://api.wordsmith.ai/api/v1/templates' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/templates", { method: "GET", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, }); const templates = await response.json(); console.log(templates); ``` ```python Python theme={null} import requests url = "https://api.wordsmith.ai/api/v1/templates" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } response = requests.get(url, headers=headers) templates = response.json() print(templates) ``` ```json Response theme={null} [ { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Employment Agreement Template", "description": "Standard employment agreement template with customizable terms and conditions", "url": "https://app.wordsmith.ai/blueprints/123e4567-e89b-12d3-a456-426614174000" }, { "id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "name": "NDA Template", "description": "Non-disclosure agreement template for protecting confidential information", "url": "https://app.wordsmith.ai/blueprints/987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f" }, { "id": "abcdef12-3456-7890-abcd-ef1234567890", "name": "Service Agreement", "description": null, "url": "https://app.wordsmith.ai/blueprints/abcdef12-3456-7890-abcd-ef1234567890" } ] ``` ## Response Fields **id** `string` The unique identifier for the template **name** `string` The display name of the template **description** `string | null` A description of the template's purpose and content. Can be null if no description is provided. **url** `string` Opens the template in Wordsmith. ## Notes * Only templates that you have read access to will be returned * The list includes both your organization's custom templates and any global templates you have access to * Templates contain placeholder fields that can be filled in to generate customized documents * Each template supports various document formats including DOCX and Markdown # Get User Information Source: https://wordsmithai.mintlify.app/api-reference/user/get-me GET https://api.wordsmith.ai/api/v1/me Retrieve information about the authenticated user and their organization Retrieves information about the currently authenticated user and their associated customer organization. ## Authentication This endpoint requires a valid API key in the Authorization header. ```bash cURL theme={null} curl -X GET "https://api.wordsmith.ai/api/v1/me" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.wordsmith.ai/api/v1/me", { method: "GET", headers: { Authorization: "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json", }, }); const userData = await response.json(); ``` ```python Python theme={null} import requests headers = { "Authorization": "Bearer sk-ws-api1-your_api_key_here", "Content-Type": "application/json" } response = requests.get("https://api.wordsmith.ai/api/v1/me", headers=headers) user_data = response.json() ``` ## Response **user\_id** `string` The unique identifier for the user **customer\_id** `string` The unique identifier for the customer organization **user\_name** `string` The display name of the user (may be null) **user\_email** `string` The email address of the user **customer\_name** `string` The name of the customer organization ```json Response theme={null} { "user_id": "123e4567-e89b-12d3-a456-426614174000", "customer_id": "987fcdeb-51a2-43d1-9e8f-7b6c5a4d3e2f", "user_name": "John Doe", "user_email": "john@lawfirm.com", "customer_name": "Example Law Firm LLC" } ``` ## Error Responses ```json 401 Unauthorized theme={null} { "error": "Invalid API key", "status": 401 } ``` ```json 429 Too Many Requests theme={null} { "error": "Rate limit exceeded", "status": 429 } ``` ## Use Cases * **Authentication verification**: Confirm that your API key is working correctly * **User context**: Get information about the current user for personalization * **Organization identification**: Determine which organization the user belongs to # Authentication Source: https://wordsmithai.mintlify.app/authentication Learn how to authenticate your API requests using API keys with the Wordsmith API. The Wordsmith API uses API keys for authentication. All API requests must include a valid API key in the Authorization header. > [!NOTE] > > Your role decides whether you can use the API. Admins and full users can by default. Viewers cannot. An admin can allow it in a custom role with the **Use the Wordsmith API** permission. Without it, you cannot create an API key, and every request returns `403 public_api_not_permitted`. ## Getting Your API Key 1. Log in to your Wordsmith dashboard at [app.wordsmith.ai](https://app.wordsmith.ai) 2. Navigate to the API section in your account settings 3. Generate a new API key 4. Copy and store your API key securely > [!WARNING] > > Keep your API key secure and never share it publicly. Treat it like a > password. ## Using Your API Key Include your API key in the `Authorization` header of every request using the Bearer token format: ```bash theme={null} Authorization: Bearer sk-ws-api1-your_api_key_here ``` ## Example Request Here's an example of making an authenticated request to get your user information: ```bash theme={null} curl -X GET "https://api.wordsmith.ai/api/v1/me" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" ``` ## API Key Format Wordsmith API keys have the following format: * They start with the prefix `sk-ws-api1-` * Followed by a unique identifier Example: `sk-ws-api1-abc123def456ghi789` ## Error Responses If authentication fails, you'll receive a `401 Unauthorized` response with a JSON body. If Wordsmith could not check the key at all, for example during a brief storage outage, you get `503 temporarily_unavailable` instead: retry, the key may be fine. See the [Errors](/errors) page for the full error format reference. ### Missing Authorization Header ```json theme={null} { "error_code": "unauthorized", "ws_api_error_code": "unauthorized", "message": "Missing Authorization header" } ``` ### Invalid API Key Format ```json theme={null} { "error_code": "unauthorized", "ws_api_error_code": "unauthorized", "message": "Invalid API key format" } ``` ### Invalid API Key ```json theme={null} { "error_code": "unauthorized", "ws_api_error_code": "unauthorized", "message": "Invalid API key" } ``` ## Security Best Practices * **Store securely**: Keep your API key in environment variables or secure configuration files * **Use HTTPS**: Always make requests over HTTPS * **Rotate regularly**: Periodically rotate your API keys * **Monitor usage**: Keep track of your API usage to detect any unauthorized access * **Restrict access**: Only use API keys in server-side applications, never in client-side code ## Rate Limiting API keys are subject to rate limits based on your subscription plan. When you exceed these limits, you'll receive a `429 Too Many Requests` response. # Errors Source: https://wordsmithai.mintlify.app/errors Understanding error responses from the Wordsmith API All Wordsmith API error responses follow a consistent JSON format. This page documents the error envelope, all possible error codes, and how to handle errors in your integration. ## Error Response Format Every error response includes a JSON body with the following structure: ```json theme={null} { "error_code": "invalid_request_body", "ws_api_error_code": "invalid_request_body", "message": "Request body is invalid. Ensure it is valid JSON with a 'question' field." } ``` **error\_code** `string` *required* A machine-readable code identifying the type of error. Use this for programmatic error handling. **ws\_api\_error\_code** `string` *required* Legacy alias for `error_code`. Both fields always contain the same value. New integrations should use `error_code`. **message** `string` A human-readable description of the error. Useful for debugging and logging. Not all errors include a message. > [!NOTE] > > **OAuth tokens.** When a request carries an OAuth access token (see [OAuth](/oauth)), a missing, invalid or expired token, or one without the route's scope, gets the OAuth error format instead: `{"error": "invalid_token" | "insufficient_scope", "error_description": "..."}` with a `WWW-Authenticate` header that names the needed scope and where to find the server's metadata. Every other error from an OAuth request uses the format above. ## Error Codes Reference ### Authentication & Authorization | HTTP Status | Error Code | Description | | - | - | - | | 401 | `unauthorized` | API key is missing, malformed, or invalid. | | 403 | `forbidden` | The API key does not have permission to perform this action. | | 403 | `public_api_not_permitted` | Your role does not allow use of the API. Ask an admin to add the **Use the Wordsmith API** permission to your role. | ### Request Validation | HTTP Status | Error Code | Description | | - | - | - | | 400 | `invalid_request_body` | The request body is not valid JSON or is missing required fields. | | 400 | `bad_request` | Generic bad request. Check the `message` field for details. | ### File Upload | HTTP Status | Error Code | Description | | - | - | - | | 400 | `invalid_upload_job` | The `upload_job_id` is not recognized, has expired, or the file was not uploaded to the presigned URL. Generate a new upload URL and retry. | | 400 | `upload_too_large` | The file is over its size limit: 1 GB for an uploaded file, 100 MB for a `url` attachment. The response's `size_limit_bytes` field carries the limit in bytes. | | 400 | `invalid_attachment_url` | A task attachment `url` could not be downloaded: it is not a public http(s) URL, did not return status 200, or took longer than 20 seconds. | | 400 | `unsupported_file_type` | The specified `content_type` is not supported. See [supported file types](/api-reference/files/upload-url#supported-file-types). | ### Questions & Sessions | HTTP Status | Error Code | Description | | - | - | - | | 400 | `session_still_processing` | A previous question in this session has not finished processing. Wait for it to complete before submitting a new question. | | 404 | `resource_not_found` | The requested agent, question, or other resource was not found. | ### Artifacts | HTTP Status | Error Code | Description | | - | - | - | | 400 | `artifact_layout_invalid` | The layout failed validation. `errors` lists every problem as `{path, code, message}`, and `warnings` lists problems that did not block the write. See [Update Artifact](/api-reference/artifacts/update-artifact). | | 400 | `artifact_layout_too_large` | The layout is over 2 MB. | | 400 | `artifact_layout_read_only` | The artifact has data sources, so its layout cannot be replaced through the API. Its name and description can still be changed. | | 409 | `artifact_version_conflict` | The artifact changed since you read it. `current_version` is its version now, and can equal the version you sent when the other edit was a rename. Fetch it again, reapply your change and retry. | ### Tasks | HTTP Status | Error Code | Description | | - | - | - | | 400 | `task_thread_reply_partially_delivered` | The reply reached the requester, but Wordsmith could not confirm everything completed. Do not retry: the requester would receive it twice. Check the requester thread before sending a follow-up. See [Reply to Requester](/api-reference/tasks/add-thread-reply). | ### Rate Limiting & Credits | HTTP Status | Error Code | Description | | - | - | - | | 429 | `rate_limit_exceeded` | Too many requests. Wait for the number of seconds in the `Retry-After` header, then retry. The body also includes `retry_after_seconds`, `limit`, and `limit_window_seconds`, plus `next_tier_with_more_credits` if a higher plan is available. See [Rate Limits](/rate-limits). | | 402 | `insufficient_credits` | Your account does not have enough credits for this operation. The response includes `credits_needed` and `total_remaining`. | ### Server Errors | HTTP Status | Error Code | Description | | - | - | - | | 500 | `internal_error` | An unexpected server error occurred. If this persists, please contact support. | | 503 | `temporarily_unavailable` | Wordsmith could not check your API key just now. The key may be valid: retry after the `Retry-After` seconds. | | 503 | `artifact_export_unavailable` | An artifact export could not be finished: it ran out of time, the renderer was briefly unavailable or the stored layout could not be fetched. Retry after a short wait. | ## Handling Errors ### Recommended Approach 1. **Check the HTTP status code** to determine the error category (4xx = client error, 5xx = server error). 2. **Switch on `error_code`** for specific handling logic (e.g., retrying on `rate_limit_exceeded`, re-uploading on `invalid_upload_job`). 3. **Log the `message`** for debugging purposes. ### Retry Strategy * **429 (rate\_limit\_exceeded)**: Wait for the `Retry-After` header's value, then retry, backing off exponentially if you are limited again. See [Rate Limits](/rate-limits). * **500 (internal\_error)**: Retry up to 3 times with backoff. If the error persists, contact support. * **503 (artifact\_export\_unavailable, temporarily\_unavailable)**: Retry after a short wait, backing off if it happens again. * **400/401/403/404**: Do not retry -- fix the request or credentials first. * **400 (task\_thread\_reply\_partially\_delivered)**: Never retry. The reply was already sent. ### Example Error Handling ```python theme={null} import time import requests response = requests.post( "https://api.wordsmith.ai/api/v1/agents/default/questions", headers={"Authorization": "Bearer sk-ws-api1-your_key"}, json={"question": "Review this contract"}, ) if response.status_code == 200: result = response.json() elif response.status_code == 429: # Wait exactly as long as we are asked to, then retry time.sleep(int(response.headers.get("Retry-After", 1))) else: error = response.json() error_code = error.get("error_code", "unknown") message = error.get("message", "No details available") print(f"API error ({error_code}): {message}") ``` # Wordsmith API Source: https://wordsmithai.mintlify.app/index Welcome to the Wordsmith API documentation. Integrate legal AI capabilities into your applications. ## Quick Links > **[Get Started](/introduction)** > > Learn about Wordsmith and get started with the API > **[Authentication](/authentication)** > > Set up API authentication with your API keys > **[Quickstart Guide](/quickstart)** > > Get up and running in just a few minutes > **[API Reference](/api-reference/user/get-me)** > > Explore all available API endpoints ## What is Wordsmith? Wordsmith is a legal AI assistant designed to automate repetitive tasks for lawyers. Our platform helps legal professionals with: * Document summarization * Legal document review * Contract analysis * Research assistance * And much more ## API Features * **RESTful API** - Simple HTTP requests and responses * **Bearer Token Authentication** - Secure API key authentication * **File Upload Support** - Submit documents for analysis * **Async Processing** - Handle long-running tasks efficiently * **Comprehensive Documentation** - Interactive API playground included # Introduction Source: https://wordsmithai.mintlify.app/introduction Learn about Wordsmith's legal AI capabilities and how the Public API can integrate with your applications. ## What is Wordsmith? Wordsmith is a legal AI assistant designed to automate repetitive tasks for lawyers. Our platform helps legal professionals with: * Document summarization * Legal document review * Research and analysis * Question answering with legal context * And much more ## API Overview The Wordsmith API provides programmatic access to our AI assistant capabilities. You can: * Ask questions and get AI-powered legal insights * Upload and process documents * Retrieve user and account information * Integrate Wordsmith's capabilities into your existing workflows ## Base URL All API requests should be made to: ``` https://api.wordsmith.ai/api/v1 ``` This URL works for every organisation. If your organisation's data is held in the United States, you can call the US region directly instead: ``` https://api.us.wordsmith.ai/api/v1 ``` Either URL reaches your data, which stays in its region. A request sent to the other region is forwarded to yours, which adds a little latency. Use the URL for your region when you can. ## Getting Started To get started with the Wordsmith API: 1. **Sign up** for a Wordsmith account at [app.wordsmith.ai](https://app.wordsmith.ai) 2. **Generate an API key** from your dashboard 3. **Authenticate** your requests using the API key 4. **Start making requests** to our endpoints ## Support If you need help or have questions about the API, please: * Email us at [support@wordsmith.ai](mailto:support@wordsmith.ai) * Check our [documentation](https://docs.wordsmith.ai) ## Rate Limits API usage is subject to per-organisation rate limits. See [Rate Limits](/rate-limits) for the current values and how to handle them, or contact our team about higher limits for enterprise use cases. # Quickstart Source: https://wordsmithai.mintlify.app/quickstart Get up and running with the Wordsmith API in just a few minutes with this step-by-step guide. ## Prerequisites Before you begin, make sure you have: * A Wordsmith account ([sign up here](https://app.wordsmith.ai)) * An API key (see [Authentication](/authentication)) * A tool to make HTTP requests (curl, Postman, or your preferred programming language) ## Step 1: Test Your Authentication First, let's verify your API key works by getting your user information: ```bash theme={null} curl -X GET "https://api.wordsmith.ai/api/v1/me" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" ``` You should receive a response like: ```json theme={null} { "user_id": "12345", "customer_id": "67890", "user_name": "John Doe", "user_email": "john@example.com", "customer_name": "Example Law Firm" } ``` ## Step 2: Ask Your First Question Now ask an agent a legal question: ```bash theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/agents/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "What are the key elements of a valid contract?", "sync_mode": true }' ``` The response will include: ```json theme={null} { "id": "session_123", "status": "completed", "answer": "A valid contract typically requires four key elements: 1) Offer - A clear proposal...", "attachments": null } ``` ## Step 3: Upload and Analyze a Document You can also upload documents for analysis. First, get an upload URL: ```bash theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/files/upload-url" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "file_name": "contract.pdf", "content_type": "application/pdf" }' ``` Then upload your file to the returned URL and use the job ID in your question: ```bash theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/agents/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "Please review this contract and summarize the key terms", "attachments": [ { "upload_job_id": "upload_job_123" } ], "sync_mode": false, "callback_url": "https://your-app.com/webhooks/wordsmith" }' ``` ## Sync vs Async Mode ### Sync Mode (`sync_mode: true`) * Response includes the complete answer immediately * Limited to 30 seconds processing time * Best for simple questions without attachments * Good for testing and development ### Async Mode (`sync_mode: false`) * Returns immediately with `status: "in_progress"` * Longer processing time for complex questions * Requires polling or callback URL for results * Recommended for production use ## Error Handling Always check for errors in your responses: ```javascript theme={null} if (response.status >= 400) { console.error("API Error:", response.data); // Handle error appropriately } ``` Common error status codes: * `400` - Bad Request (invalid parameters) * `401` - Unauthorized (invalid API key) * `429` - Too Many Requests (rate limit exceeded) * `500` - Internal Server Error ## Next Steps Now that you've made your first API calls: 1. **Explore the API Reference** - See all available endpoints and parameters 2. **Set up webhooks** - Use callback URLs to receive async results 3. **Integrate with your app** - Build Wordsmith into your workflow 4. **Monitor usage** - Keep track of your API usage and rate limits ## SDK and Libraries While we don't currently provide official SDKs, the API works with any HTTP client library in your preferred programming language: * **Python**: `requests`, `httpx` * **JavaScript/Node.js**: `fetch`, `axios` * **PHP**: `cURL`, `Guzzle` * **Ruby**: `net/http`, `Faraday` * **Java**: `OkHttp`, `Apache HttpClient` ## Need Help? If you run into issues: * Check the [API Reference](/api-reference) for detailed endpoint documentation * Email us at [support@wordsmith.ai](mailto:support@wordsmith.ai) # Rate Limits Source: https://wordsmithai.mintlify.app/rate-limits The limits that apply to your API usage, and how to handle them The Wordsmith API applies rate limits per organisation so that one integration's traffic burst cannot slow the platform down for anyone else. The limits are deliberately generous: a well-behaved integration should never see one. ## Current Limits | Limit | Scope | Value | | - | - | - | | New sessions | Per organisation | 60 per minute | | API requests | Per organisation | 400 per minute | A few things worth knowing about how these behave: * **They are shared across your whole organisation**, not per API key or per user. Creating additional keys does not raise your limit. * **They refill continuously**, rather than resetting on a fixed schedule. The "new sessions" limit means you can submit 60 new questions at once, after which capacity returns at roughly one per second. There is no cliff edge at the top of the minute. * **Only new sessions count towards the session limit.** Follow-up questions sent to an existing session — by passing `session_id` to [Create Question](/api-reference/agents/create-question) — count only towards the overall request limit. Grouping related questions into one session is both faster and cheaper on your limit. * **Polling counts as a request**, and it is what most integrations spend their request limit on. Each call to [Get Question Status](/api-reference/agents/get-question-status) uses one, and polling has to share the limit with the calls that create the questions in the first place. At one poll per question every ten seconds, around 30 questions in flight leaves you comfortable headroom; polling every second, six or seven questions is all your budget stretches to. If you need to track more than that at once, **pass a `callback_url`** and let a [webhook](/webhooks) tell you when each answer is ready — that removes polling from your budget entirely, and it is the approach we recommend for any integration running at volume. ## When You Are Rate Limited The API responds with HTTP `429` and a `Retry-After` header telling you exactly how many seconds to wait: ```http theme={null} HTTP/1.1 429 Too Many Requests Retry-After: 1 Content-Type: application/json { "error_code": "rate_limit_exceeded", "message": "Rate limit exceeded: at most 60 new sessions created by your organisation per minute. Retry in 1 second, then back off exponentially if you are limited again.", "retry_after_seconds": 1, "limit": 60, "limit_window_seconds": 60 } ``` **retry\_after\_seconds** `integer` Seconds to wait before retrying. Always matches the `Retry-After` header. **limit** `integer` The maximum number of operations allowed in the window. **limit\_window\_seconds** `integer` The length of the window the limit applies over, in seconds. A `429` means the request was rejected before any work started, so it is always safe to retry. ## Handling Limits Gracefully Honour `Retry-After`, then back off exponentially if you are limited repeatedly. This keeps your integration moving at the fastest rate we can serve without you having to guess at a delay. ```python theme={null} import time import requests def create_question(payload, max_attempts=6): delay = None for attempt in range(max_attempts): response = requests.post( "https://api.wordsmith.ai/api/v1/agents/default/questions", headers={"Authorization": "Bearer sk-ws-api1-your_key"}, json=payload, ) if response.status_code != 429: response.raise_for_status() return response.json() # Wait at least as long as we are asked to, backing off further if we are limited again. retry_after = int(response.headers.get("Retry-After", 1)) delay = retry_after if delay is None else min(max(retry_after, delay * 2), 60) time.sleep(delay) raise RuntimeError("Still rate limited after retrying") ``` If you are queuing a large batch of work, pacing your submissions to stay under the limit is better than submitting everything and retrying the rejections — you will finish sooner and with fewer wasted calls. ## Need a Higher Limit? If your use case genuinely needs more headroom, email [support@wordsmith.ai](mailto:support@wordsmith.ai) with a description of your workload and the throughput you need. We would much rather raise your limit than have you throttled. # Webhooks Source: https://wordsmithai.mintlify.app/webhooks Learn how to receive real-time notifications from Wordsmith using webhooks with signature verification for security. ## About webhooks Webhooks allow you to receive real-time notifications when asynchronous operations complete in Wordsmith. Instead of polling the API to check if a task is finished, you can configure a webhook URL to receive callbacks when processing completes. ## How webhooks work 1. **Configure a webhook URL**: When making an API request, include a `callback_url` parameter 2. **Receive notifications**: Wordsmith will send a POST request to your URL when processing completes 3. **Verify signatures**: Use the webhook secret to verify that requests are genuinely from Wordsmith 4. **Process the data**: Handle the notification data in your application ## Setting up webhooks ### Step 1: Create an API key with webhook secret When creating an API key, you can optionally provide a webhook secret. If you don't provide one, Wordsmith will automatically generate a secure secret for you. ### Step 2: Use webhooks in your requests Include a `callback_url` when making async requests (`sync_mode` is `false`): ```bash theme={null} curl -X POST "https://api.wordsmith.ai/api/v1/agents/default/questions" \ -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "question": "Please analyze this contract", "attachments": [ { "upload_job_id": "upload_abc123def456" } ], "sync_mode": false, "callback_url": "https://your-app.com/webhooks/wordsmith" }' ``` ## Webhook payload format When processing completes, Wordsmith sends a POST request to your callback URL with the following payload: ```json theme={null} { "id": "session_abc123", "status": "completed", "answer": "Based on my analysis of the contract...", "attachments": [ { "file_name": "contract_analysis.pdf", "content_type": "application/pdf", "content_length": 245760, "url": "https://files.wordsmith.ai/signed-url-here" } ], "reason": null } ``` A question the agent declined is delivered the same way, so a webhook-driven integration is never left waiting for a run that will never happen. Nothing was processed, so the session fields are `null`, and `reason` says why the question was turned away: ```json theme={null} { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "session_id": null, "session_url": null, "status": "filtered", "answer": null, "attachments": null, "reason": "This agent only handles commercial contract questions, and this one is about employment law." } ``` ### Payload fields **id** `string` The unique session ID for the completed question **session\_id** `string` The session the question was answered in. `null` only when `status` is `"filtered"`, where no session was created **session\_url** `string` A direct URL to view the session in the Wordsmith web application. `null` only when `status` is `"filtered"` **status** `string` The final status: `"completed"`, `"error"`, or `"filtered"` — the agent declined the question, which is not an error (see [Filtered questions](/api-reference/agents/create-question#filtered-questions)) **answer** `string` The agent's response. Present when `status` is `"completed"`, contains error message when `status` is `"error"`, and is `null` when `status` is `"filtered"` **attachments** `array` Array of generated files (e.g., analysis reports, summaries). Only present when `status` is `"completed"`. Each `url` expires 5 minutes after the callback is sent; poll the question for fresh ones **reason** `string` One sentence explaining why the agent declined the question. Present only when `status` is `"filtered"`, and `null` for every other status. It is the same sentence the create and poll responses carry, and can be `null` even on a filtered question — see [Filtered questions](/api-reference/agents/create-question#filtered-questions) ## Validating webhook signatures To ensure webhook requests are genuinely from Wordsmith and haven't been tampered with, you should validate the signature before processing the payload. ### About signature validation Wordsmith uses HMAC-SHA256 to sign webhook payloads with your webhook secret. The signature is included in the `Wordsmith-Signature` header and follows this format: ``` t=1234567890,v1=abc123def456... ``` Where: * `t` is the timestamp when the signature was created * `v1` is the HMAC-SHA256 signature ### Signature verification process 1. Extract the timestamp and signature from the `Wordsmith-Signature` header 2. Create the signed payload by concatenating the timestamp and the request body: `{timestamp}.{payload}` 3. Generate the expected signature using HMAC-SHA256 with your webhook secret 4. Compare the expected signature with the received signature using a constant-time comparison 5. Verify the timestamp is within 60 seconds of the current time ### Example implementations #### Python ```python theme={null} import hashlib import hmac import time from typing import Optional def verify_webhook_signature(payload: str, signature_header: str, secret: str) -> bool: """ Verify that the webhook payload was sent from Wordsmith. Args: payload: The raw request body signature_header: The Wordsmith-Signature header value secret: Your webhook secret Returns: True if signature is valid, False otherwise """ if not signature_header: return False # Parse the signature header try: # Extract timestamp and signature parts = signature_header.split(',') timestamp_part = parts[0] signature_part = parts[1] timestamp = int(timestamp_part.split('=')[1]) signature = signature_part.split('=')[1] except (IndexError, ValueError): return False # Check timestamp tolerance (60 seconds) current_time = int(time.time()) if abs(current_time - timestamp) > 60: return False # Create the signed payload signed_payload = f"{timestamp}.{payload}" # Generate expected signature expected_signature = hmac.new( secret.encode('utf-8'), signed_payload.encode('utf-8'), hashlib.sha256 ).hexdigest() # Compare signatures using constant-time comparison return hmac.compare_digest(signature, expected_signature) # Usage in Flask/FastAPI @app.route('/webhooks/wordsmith', methods=['POST']) def webhook_handler(): payload = request.get_data(as_text=True) signature = request.headers.get('Wordsmith-Signature') if not verify_webhook_signature(payload, signature, 'your_webhook_secret'): return 'Invalid signature', 401 # Process the webhook payload data = request.get_json() # ... handle the webhook data return 'OK', 200 ``` #### JavaScript/Node.js ```javascript theme={null} const crypto = require("crypto"); function verifyWebhookSignature(payload, signatureHeader, secret) { if (!signatureHeader) { return false; } try { // Parse the signature header const parts = signatureHeader.split(","); const timestampPart = parts[0]; const signaturePart = parts[1]; const timestamp = parseInt(timestampPart.split("=")[1]); const signature = signaturePart.split("=")[1]; // Check timestamp tolerance (60 seconds) const currentTime = Math.floor(Date.now() / 1000); if (Math.abs(currentTime - timestamp) > 60) { return false; } // Create the signed payload const signedPayload = `${timestamp}.${payload}`; // Generate expected signature const expectedSignature = crypto .createHmac("sha256", secret) .update(signedPayload, "utf8") .digest("hex"); // Compare signatures using constant-time comparison return crypto.timingSafeEqual( Buffer.from(signature, "hex"), Buffer.from(expectedSignature, "hex") ); } catch (error) { return false; } } // Usage in Express app.post( "/webhooks/wordsmith", express.raw({ type: "application/json" }), (req, res) => { const payload = req.body.toString(); const signature = req.headers["wordsmith-signature"]; if (!verifyWebhookSignature(payload, signature, "your_webhook_secret")) { return res.status(401).send("Invalid signature"); } // Process the webhook payload const data = JSON.parse(payload); // ... handle the webhook data res.send("OK"); } ); ``` #### Ruby ```ruby theme={null} require 'openssl' require 'time' def verify_webhook_signature(payload, signature_header, secret) return false unless signature_header begin # Parse the signature header parts = signature_header.split(',') timestamp_part = parts[0] signature_part = parts[1] timestamp = timestamp_part.split('=')[1].to_i signature = signature_part.split('=')[1] # Check timestamp tolerance (60 seconds) current_time = Time.now.to_i return false if (current_time - timestamp).abs > 60 # Create the signed payload signed_payload = "#{timestamp}.#{payload}" # Generate expected signature expected_signature = OpenSSL::HMAC.hexdigest( OpenSSL::Digest.new('sha256'), secret, signed_payload ) # Compare signatures using constant-time comparison Rack::Utils.secure_compare(signature, expected_signature) rescue false end end # Usage in Rails/Sinatra post '/webhooks/wordsmith' do payload = request.body.read signature = request.env['HTTP_WORDSMITH_SIGNATURE'] unless verify_webhook_signature(payload, signature, 'your_webhook_secret') halt 401, 'Invalid signature' end # Process the webhook payload data = JSON.parse(payload) # ... handle the webhook data 'OK' end ``` ## Testing webhook signatures You can test your signature verification implementation using these values: * **Secret**: `whsec_test_secret_123` * **Payload**: `{"id":"test","status":"completed"}` * **Timestamp**: `1234567890` * **Expected signature**: `c60c0cc7241d79e8bf2a88fdc6ce257c2fd547048bb244495309b27ad07884bf` * **Header value**: `t=1234567890,v1=c60c0cc7241d79e8bf2a88fdc6ce257c2fd547048bb244495309b27ad07884bf` ## Security best practices ### Store secrets securely * Never hardcode webhook secrets in your application * Use environment variables or secure configuration management * Rotate webhook secrets periodically ### Validate all webhooks * Always verify webhook signatures before processing * Use constant-time comparison functions to prevent timing attacks * Check timestamp tolerance to prevent replay attacks ### Handle errors gracefully * Return appropriate HTTP status codes (401 for invalid signatures) * Log failed signature verifications for monitoring * Don't expose sensitive information in error messages ### Use HTTPS * Always use HTTPS for webhook endpoints * Validate that callback URLs use secure protocols * Consider using certificate pinning for additional security ## Troubleshooting ### Common issues **Signature verification fails** * Ensure you're using the correct webhook secret * Check that the payload hasn't been modified by middleware * Verify you're parsing the signature header correctly **Webhook not received** * Check that your endpoint is publicly accessible * Verify the callback URL is correct and uses HTTPS * Ensure your server can handle POST requests **Timestamp validation fails** * Check that your server's clock is synchronized * Verify you're using the correct tolerance (60 seconds) * Ensure you're parsing the timestamp correctly ### Testing locally For local development, you can use tools like ngrok to expose your local server: ```bash theme={null} # Install ngrok npm install -g ngrok # Expose your local server ngrok http 3000 # Use the ngrok URL as your callback_url # https://abc123.ngrok.io/webhooks/wordsmith ``` ## Rate limiting Webhook delivery is subject to rate limits to prevent abuse. If you receive too many webhooks in a short period, some may be dropped. Implement idempotency in your webhook handlers to handle potential duplicate deliveries. ## Next steps * [API Reference](/api-reference) - Explore all available endpoints * [Authentication](/authentication) - Learn about API key authentication * [Quickstart](/quickstart) - Get started with the Wordsmith API