DEVELOPER DOCUMENTATION
Audit Job Messages
One read over every conversation for a job: proposal messages, job messages, and channels, with who still needs a reply from your team.
/api/public/v1/messages/auditReads every conversation for one job in a single call: proposal messages, job messages after a hire, and job channels. Use it to answer “who is still waiting on us for this job?” and to review the full message history of a job.
The response has two parts:
conversationsalways covers the whole job. Each summary says whether the conversation still needs a reply from your team (needsEmployerReply). The message filters below never hide a conversation that is waiting on you. One exception: a direct conversation with a banned account is left out, and a banned account’s messages are not returned or counted anywhere in the response.messagesis a cursor-paged stream of complete message bodies, newest first, with the sender’s name, role, and whether they belong to your organization.
This endpoint is read-only: it never creates or changes a conversation. For the full replies of one thread, use GET /messages/thread. Attachments appear as opaque metadata only; to download one, pass its attachments[].attachmentId to GET /messages/{messageId}/attachments/{attachmentId}, and read privacy and sensitive files first.
Requirements: messages:read scope and employer access to the job.
How needsEmployerReply works
Section titled “How needsEmployerReply works”needsEmployerReply is decided for your organization, not for one person:
- A reply from any teammate in your organization answers the conversation.
- A conversation where only the freelancer has written stays open.
- A freelancer follow-up after a teammate’s reply opens it again.
- An older thread that nobody answered keeps its conversation open, even when your team posted newer top-level messages.
- Only person-written messages count. System events, deleted messages and moderated messages never open or answer a conversation.
It describes who wrote last. It does not say whether the underlying question is resolved.
Request
Section titled “Request”jobIdstringqueryrequiredThe job to audit.
contextTypestringqueryproposal_dm (messages with a candidate before a hire), job_dm (messages with a hired freelancer), or channel. Limits both conversations and messages.
sincestringqueryISO timestamp. Only messages sent at or after this time.
untilstringqueryISO timestamp. Only messages sent at or before this time.
searchstringqueryCase-insensitive text to find in message content (1–500 characters).
senderUserIdstringqueryOnly messages from this user.
hasAttachmentsbooleanquerytrue returns only messages with attachments; false only messages without.
includeThreadsbooleanquerytruefalse returns top-level messages only, without thread replies.
limitnumberquery50Page size, 1–100. For a large job, use a smaller page (10–25) and follow nextCursor.
cursorstringquerynextCursor from the previous page.
since, until, search, senderUserId, hasAttachments, and includeThreads filter the messages stream only. contextType filters both parts.
Response
Section titled “Response”asOfstringISO time of the read.
jobIdstringThe audited job.
employerOrganizationUserIdsstring[]User IDs that count as your organization for needsEmployerReply.
conversationsobject[]messagesobject[]Newest first. Each entry is a message record:
nextCursorstring | nullPass back as cursor for the next page; null at the end.
Errors
Section titled “Errors”| Status | code | Meaning |
|---|---|---|
400 | BAD_REQUEST | Invalid query parameters. A limit outside 1–100 says so in the message and details. An unknown cursor returns details.reason: "UNKNOWN_CURSOR" |
401 | UNAUTHORIZED | Missing or invalid token |
403 | FORBIDDEN | Missing messages:read scope, or no employer access to the job (details.reason: "job_access_denied" or "not_a_participant") |
404 | NOT_FOUND | No such job (details.reason: "job_missing") |
503 | SERVICE_UNAVAILABLE | A database dependency was briefly unavailable. Retry the same request after Retry-After |
CLI, MCP, and SDK
Section titled “CLI, MCP, and SDK”- CLI:
opentrain messages audit --job-id <JOB_ID>with--context,--since,--until,--search,--sender,--has-attachments,--no-threads,--cursor, and--limit. - MCP:
opentrain_audit_job_messages, with the same parameter names as the REST query. - TypeScript SDK:
opentrain.auditJobMessages({ jobId, ... }).