Skip to content
OpenTrain AIOpenTrain AIOpenTrain AIDocs

Ask OpenTrain

Answers from the documentation, with sources.

What would you like to do with OpenTrain?

AI answers can be mistaken. Check the linked sources. Don’t include private account information.

Open app

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.

GET/api/public/v1/messages/audit

Reads 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:

  • conversations always 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.
  • messages is 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.

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.

jobIdstringqueryrequired

The job to audit.

contextTypestringquery

proposal_dm (messages with a candidate before a hire), job_dm (messages with a hired freelancer), or channel. Limits both conversations and messages.

sincestringquery

ISO timestamp. Only messages sent at or after this time.

untilstringquery

ISO timestamp. Only messages sent at or before this time.

senderUserIdstringquery

Only messages from this user.

hasAttachmentsbooleanquery

true returns only messages with attachments; false only messages without.

includeThreadsbooleanquery
Default: true

false returns top-level messages only, without thread replies.

limitnumberquery
Default: 50

Page size, 1–100. For a large job, use a smaller page (10–25) and follow nextCursor.

cursorstringquery

nextCursor from the previous page.

since, until, search, senderUserId, hasAttachments, and includeThreads filter the messages stream only. contextType filters both parts.

asOfstring

ISO time of the read.

jobIdstring

The audited job.

employerOrganizationUserIdsstring[]

User IDs that count as your organization for needsEmployerReply.

conversationsobject[]
conversation summary
idstring

Conversation ID.

contextTypestring

proposal_dm, job_dm, or channel.

jobIdstring | null

Linked job.

proposalIdstring | null

Linked proposal, for proposal conversations.

channelNamestring | null

Channel name, for channels.

counterpartobject | null

{userId, displayName} of the freelancer or candidate in a direct conversation; null for channels.

contractobject | null

{id, status} for job messages after a hire, where status is active or ended; null otherwise.

needsEmployerReplyboolean

true when the conversation is waiting on your organization. See How needsEmployerReply works.

latestActivityAtstring | null

ISO time of the latest activity.

messagesobject[]

Newest first. Each entry is a message record:

message record
idstring

Message ID.

conversationIdstring

The conversation.

contextTypestring

proposal_dm, job_dm, or channel.

threadIdstring | null

Thread this message replies in; null for a top-level message.

createdAtstring

ISO sent time.

updatedAtstring

ISO last-change time.

editedAtstring | null

ISO time of the last edit; null if never edited.

statestring

visible, deleted, or moderated.

contentstring | null

Full message text; null for deleted and moderated messages.

messageTypestring | null

Type of a system or automated message.

systemobject | null

{blueText, greyText} for system notices; null for ordinary messages.

senderobject

{userId, displayName, role, employerOrganizationMember, isFromViewer}. role is freelancer, employer_member, employer_owner, platform_admin, system, or unknown.

attachmentsobject[]

Opaque attachment metadata, the same shape as in GET /messages. Never a URL or storage path. Deleted and moderated messages return [].

hasAudioboolean

true when the message has an audio recording.

reactionCountnumber

Number of emoji reactions.

threadobject | null

For a top-level message that has replies: {threadId, replyCount, lastReplyAt, needsEmployerReply}. Read the replies with GET /messages/thread.

nextCursorstring | null

Pass back as cursor for the next page; null at the end.

StatuscodeMeaning
400BAD_REQUESTInvalid query parameters. A limit outside 1–100 says so in the message and details. An unknown cursor returns details.reason: "UNKNOWN_CURSOR"
401UNAUTHORIZEDMissing or invalid token
403FORBIDDENMissing messages:read scope, or no employer access to the job (details.reason: "job_access_denied" or "not_a_participant")
404NOT_FOUNDNo such job (details.reason: "job_missing")
503SERVICE_UNAVAILABLEA database dependency was briefly unavailable. Retry the same request after Retry-After
  • 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, ... }).