Skip to content

This is the staging environment's documentation. It is a draft and may change at any time.

Get a thread

GET
/threads/{threadId}
curl --request GET \
--url https://api-staging.fedwright.com/v1/threads/idvs%3ACONT_IDV_75N98024D00001_7529 \
--header 'Authorization: Bearer <token>'

The procurement thread: the records linked to an opportunity, an award, or a vehicle, with a timeline.

An opportunity’s thread id ({departmentKey}:{solicitationKey}, as always) returns the opportunity, unchanged, with the thread as procurement. A merged id resolves to the surviving thread; the body’s id may differ from the path. {dataset}:{id} of a contract, IDV, OTA, OT IDV, or vehicle returns a ProcurementThread with the same procurement.

The thread includes the links of a procurement (not a company’s other awards) of confidence 0.90 or more, at most limits.maxDepth links from the anchor and limits.maxNodes records, and says when a bound stopped it (truncated, truncatedBy). A vehicle joins a thread only as its anchor.

threadId
required

An opportunity’s thread id (ThreadId), or the anchor of another thread: contracts, idvs, otas, otidvs, or vehicles, a colon, and the record’s key, as its own endpoint names it.

string
/^(N:[0-9a-f]{32}|[A-Z0-9]+:[A-Z0-9]+|(contracts|idvs|otas|otidvs|vehicles):[^/]+)$/

The thread: an opportunity’s thread id, or {dataset}:{id} of a contract, IDV, OTA, OT IDV, or vehicle. Its colon may be sent as is or percent-encoded.

If-None-Match
string

The ETag the client holds. A match returns 304.

The thread.

Media typeapplication/json
Any of:

A thread with its most recent awards.

object
awardCount
required

The number of award notices in the thread.

integer
currentDeadline
required
One of:
object
at
required

The deadline as a point in time, or null when SAM.gov gave a date without a time.

null | string format: date-time
date
required

The deadline’s date, or null when SAM.gov gave a time as well.

null | string format: date
hasTime
required

True when SAM.gov gave a time as well as a date.

boolean
currentNaics
required

The NAICS code of the latest notice, or null when it has none.

null | string
currentPsc
required

The PSC code of the latest notice, or null when it has none.

null | string
currentSetAside
required

The set-aside of the latest notice.

object
code
required

The SAM.gov set-aside code, or null when none is set.

null | string
programs
required

The set-aside programs the code belongs to. Empty when none is set.

Array<string>
Allowed values: 8a edwosb hubzone indian_enterprise local_area sdvosb small_business vosb wosb
soleSource
required

True when the set-aside is a sole-source code.

boolean
currentType
required

SAM.gov notice type, using its published label.

string
Allowed values: Sources Sought Special Notice Presolicitation Consolidate/(Substantially) Bundle Solicitation Combined Synopsis/Solicitation Sale of Surplus Property Modification/Amendment/Cancel Award Notice Justification Justification and Approval (J&A)
departmentKey
required

The normalized key of the issuing department, or null when the notices name none.

null | string
firstPostedAt
required

When SAM.gov posted the thread’s first notice.

string format: date-time
firstSeenAt
required

When Fedwright first saw any notice of the thread.

string format: date-time
id
required

Opaque thread identifier; use the ID the API returns.

string
/^(N:[0-9a-f]{32}|[A-Z0-9]+:[A-Z0-9]+)$/
lastChangedAt
required

When the thread last changed. Before any change, it is when Fedwright first saw the thread.

string format: date-time
latestChange
required
One of:

The latest change to a procurement thread.

object
id
required

A UUIDv7.

string format: uuid
kinds
required

What the change was. A change can have more than one kind.

Array<string>
>= 1 items
Allowed values: amended corrected awarded sole_source_justified archived
occurredAt
required

When the change occurred.

string format: date-time
significance
required

How much a change matters to a responder: high, medium, or low.

string
Allowed values: high medium low
latestNoticeId
required

SAM.gov NoticeId.

string
/^[0-9a-f]{32}$/
noticeCount
required

The number of notices in the thread.

integer
>= 1
phase
required

The stage as users see it: solicitation becomes open while the deadline is in the future or missing, and closed once it has passed. Calculated when the response is built.

string
Allowed values: forecast presolicitation open closed awarded archived
soleSourceJustified
required

A Justification or J&A notice has joined the thread.

boolean
solicitationDisplay
required

The latest notice’s solicitation number, as given.

null | string
solicitationKey
required

The normalized solicitation number the thread is keyed on, or null for a thread with none.

null | string
stage
required

The stage stored on a thread.

string
Allowed values: forecast presolicitation solicitation awarded archived
awards
required

The 10 most recent awards. awardCount has the total; page through /threads/{threadId}/awards for the rest.

Array<object>
<= 10 items

An award, from an award notice.

object
amount
required
One of:

A monetary amount in one currency.

object
amount
required

Decimal amount as a string, so no precision is lost.

string
/^-?[0-9]+(\.[0-9]+)?$/
currency
required

ISO 4217 currency code.

string
/^[A-Z]{3}$/
awardDate
required

The date of the award, or null when the notice gives none.

null | string format: date
awardNumber
required

The award number, as the award notice gives it.

string
awardee
required

The awardee text as given, the name with any address removed, and the normalized name used for matching. uei and cage are filled only when the text holds a token that passes the identifier’s check.

object
cage
required

The awardee’s CAGE code, when the text holds one that passes its check. Null otherwise.

null | string
/^[A-Z0-9]{5}$/
name
required

The awardee’s name, with any address removed.

string
normalizedName
required

The name in the form matching compares it, so spelling and punctuation differences do not matter.

string
raw
required

The awardee text exactly as the award notice gives it, which may include an address.

string
uei
required

The awardee’s UEI, when the text holds one that passes its check. Null otherwise.

null | string
/^[A-HJ-NP-Z0-9]{12}$/
id
required

A UUIDv7.

string format: uuid
noticeId
required

SAM.gov NoticeId.

string
/^[0-9a-f]{32}$/
sources

Where the award came from, the entries of its award notice. Absent when source provenance is not yet available.

Array<object>

An upstream record that contributed to a record.

object
firstSeenAt
required

When this publisher first delivered the record.

string format: date-time
formatVersionId
required

The version of the publisher’s format Fedwright read the record with.

null | string
lastSeenAt
required

When this publisher last delivered the record.

string format: date-time
retrievalType
required

A source identifier, such as grantsgov.extract, for the kind of data the record came from.

string
source
required

The publisher, such as grantsgov; derived for a record Fedwright derived.

string
upstreamId
required

The publisher’s own identifier for the record, verbatim.

string
upstreamUrl
required

A link to the record at the publisher, or null when there is none.

null | string format: uri
threadId
required

Opaque thread identifier; use the ID the API returns.

string
/^(N:[0-9a-f]{32}|[A-Z0-9]+:[A-Z0-9]+)$/
links
required

The opportunity’s links to other datasets, such as the contract awards that cite its solicitation number (award-of-opportunity). GET /links?dataset=opportunities&id={threadId} pages the full graph edges with their method and confidence.

Array<object>

A related record in another dataset, or the same one, and how it relates.

object
dataset
required

The related record’s dataset, as the API names it.

string
id
required

The related record’s identifier.

string
relation
required

How the records relate, such as award-of-opportunity.

string
match

The caller organization’s match details.

object
followLevel

How the caller’s organization follows the thread, or null if it does not follow it.

null | string
Allowed values: watching pursuing
grade
required

The score band, from A (best) to D.

string
Allowed values: A B C D
score
required

The match score as a whole number from 0 to 100.

integer
<= 100
state
One of:

A user’s own state for a match.

string
Allowed values: new seen starred dismissed
procurement

The records linked to an anchor. The timeline lists each record once, oldest first, a record without a date last; records of one date go by kind (forecast, opportunity, protest, award notice, vehicle, IDV, award, order, subaward, appeal), then dataset, then id.

object
anchor
required

A record in the links graph.

object
dataset
required

The record’s dataset, as the API names it.

string
id
required

The record’s ID within its dataset.

string
edges
required

The links among the thread’s records, A-Z by relation, then source, then target.

Array<object>

A link between two records of a thread.

object
confidence
required

How sure the match is, from 0 to 1. A link on identical keys scores 1.

number
<= 1
from
required

A record in the links graph.

object
dataset
required

The record’s dataset, as the API names it.

string
id
required

The record’s ID within its dataset.

string
method
required

How the link was found, exact-key, normalized-key, or derived-key (an IDV of a vehicle).

string
relation
required

How the records relate, such as award-of-opportunity.

string
to
required

A record in the links graph.

object
dataset
required

The record’s dataset, as the API names it.

string
id
required

The record’s ID within its dataset.

string
limits
required

The bounds a thread’s walk used.

object
maxDepth
required

The most links between the anchor and a record.

integer
maxNodes
required

The most records in the thread.

integer
>= 1
minConfidence
required

The lowest confidence of a link the thread includes.

number
<= 1
skippedLinks
required

How many links of the thread’s records fell below the confidence bound and were not followed.

integer
timeline
required

The records of the thread, one entry each, oldest first. Records without a date come last.

Array<object>

One record of a thread on its timeline.

object
dataset
required

The dataset the record belongs to, as the API names it.

string
date
required

When the record happened in the procurement: an opportunity’s first posting, an award’s signing (else its effective date), an award notice’s award date, a subaward’s action date, a vehicle’s first award, a forecast’s anticipated award, an appeal’s decision. Null when the dataset has no such date or no longer holds the record.

null | string format: date-time
depth
required

How many links the record is from the anchor.

integer
id
required

The record’s ID within its dataset.

string
kind
required

What the record is: forecast, opportunity, protest, award_notice, vehicle, idv, award, order, subaward, appeal, or the dataset’s name for any other.

string
title
required

A short label, such as a solicitation number or a PIID.

null | string
truncated
required

True when a bound stopped the walk, so the thread may hold more records.

boolean
truncatedBy
required

The bounds that stopped it, A-Z: depth (records lie beyond limits.maxDepth), edges (a record has more links than one read shows), nodes (limits.maxNodes reached).

Array<string>
sources

Where the thread came from, the entries of its notices, one per upstream notice. Absent when source provenance is not yet available.

Array<object>

An upstream record that contributed to a record.

object
firstSeenAt
required

When this publisher first delivered the record.

string format: date-time
formatVersionId
required

The version of the publisher’s format Fedwright read the record with.

null | string
lastSeenAt
required

When this publisher last delivered the record.

string format: date-time
retrievalType
required

A source identifier, such as grantsgov.extract, for the kind of data the record came from.

string
source
required

The publisher, such as grantsgov; derived for a record Fedwright derived.

string
upstreamId
required

The publisher’s own identifier for the record, verbatim.

string
upstreamUrl
required

A link to the record at the publisher, or null when there is none.

null | string format: uri
timeline

The first 50 events in the thread, oldest first.

Array<object>
<= 50 items

One event in a thread’s history.

object
date
required

When the event happened.

string format: date-time
kind
required

What happened: the thread was created, a notice was amended, corrected (changed in place), awarded, or justified as sole source (sole_source_justified), or the thread was archived.

string
Allowed values: created amended corrected awarded sole_source_justified archived
noticeId
required

SAM.gov NoticeId.

string
/^[0-9a-f]{32}$/
version
required

The version of the notice the event concerns.

integer
>= 1
timelineTruncated

True when the thread has more than 50 events and timeline shows only the first 50.

boolean

Examples

Exampledefault

A thread detail

{
"awardCount": 0,
"awards": [],
"currentDeadline": {
"at": "2026-11-02T20:30:00Z",
"date": null,
"hasTime": true
},
"currentNaics": "336413",
"currentPsc": "1560",
"currentSetAside": {
"code": null,
"programs": [],
"soleSource": false
},
"currentType": "Solicitation",
"departmentKey": "DEPTOFDEFENSE",
"firstPostedAt": "2026-09-17T12:44:56Z",
"firstSeenAt": "2026-10-02T03:41:07Z",
"id": "DEPTOFDEFENSE:N0038326QFC09",
"lastChangedAt": "2026-10-03T03:41:12Z",
"latestChange": {
"id": "019aa3f2-6d10-7b20-9c4f-7e2d3cad5b12",
"kinds": [
"amended"
],
"occurredAt": "2026-10-03T03:41:12Z",
"significance": "high"
},
"latestNoticeId": "e6a41be9630d4261b6f3131923fdc4ce",
"links": [],
"noticeCount": 2,
"phase": "open",
"soleSourceJustified": false,
"solicitationDisplay": "N0038326QFC09",
"solicitationKey": "N0038326QFC09",
"stage": "solicitation"
}
ETag
string

Strong entity tag of the returned resource.

The resource has not changed since the ETag in If-None-Match.

ETag
string

Strong entity tag of the returned resource.

The request is malformed, or its schema refuses it: an unknown property, a value of the wrong type or outside its bounds, a limit above 200, or a sort outside the operation’s allow-list (Problem type bad-request). errors names each field by its path, such as nested.note, or $ for the body itself. The request is refused before anything is read or written. A cursor that was altered, has expired, or was issued for other filters, another sort, organization, or user returns Problem type invalid-cursor.

Media typeapplication/problem+json

RFC 9457 Problem Details.

object
detail

An explanation specific to this occurrence of the problem. It is absent for server errors.

string
errors

Validation errors, by field path.

object
key
additional properties
Array<string>
instance

A URI reference that identifies this occurrence of the problem, when the API provides one.

string
limit

On rate-limited, the quota that is used up, such as org-day. On premium-quota-exceeded, org-premium-day. A 429 with an empty body does not name it.

string
resetsAt

On rate-limited from the API and on premium-quota-exceeded, when that quota starts again.

string format: date-time
status
required

The HTTP status code of the response, from 400 to 599.

integer
>= 400 <= 599
title
required

A short, human-readable summary of the problem. Use type to identify the kind of problem, not this text.

string
traceId
required

W3C trace ID of the failed request.

string
type
required

https://fedwright.com/problems/{code}, the same in every environment.

string format: uri

Example generated

{
"detail": "example",
"errors": {
"additionalProperty": [
"example"
]
},
"instance": "example",
"limit": "example",
"resetsAt": "2026-04-15T12:00:00Z",
"status": 1,
"title": "example",
"traceId": "example",
"type": "https://example.com"
}

The access token is missing, expired, or invalid.

Media typeapplication/problem+json

RFC 9457 Problem Details.

object
detail

An explanation specific to this occurrence of the problem. It is absent for server errors.

string
errors

Validation errors, by field path.

object
key
additional properties
Array<string>
instance

A URI reference that identifies this occurrence of the problem, when the API provides one.

string
limit

On rate-limited, the quota that is used up, such as org-day. On premium-quota-exceeded, org-premium-day. A 429 with an empty body does not name it.

string
resetsAt

On rate-limited from the API and on premium-quota-exceeded, when that quota starts again.

string format: date-time
status
required

The HTTP status code of the response, from 400 to 599.

integer
>= 400 <= 599
title
required

A short, human-readable summary of the problem. Use type to identify the kind of problem, not this text.

string
traceId
required

W3C trace ID of the failed request.

string
type
required

https://fedwright.com/problems/{code}, the same in every environment.

string format: uri

Example generated

{
"detail": "example",
"errors": {
"additionalProperty": [
"example"
]
},
"instance": "example",
"limit": "example",
"resetsAt": "2026-04-15T12:00:00Z",
"status": 1,
"title": "example",
"traceId": "example",
"type": "https://example.com"
}

The Problem type says which check refused the caller, when that is about the caller’s own token, client, or role: insufficient-scope when the token’s scopes (or an MCP grant’s) lack the operation’s scope, with WWW-Authenticate: Bearer error="insufficient_scope", scope="..." (RFC 6750) naming the missing scopes; client-not-allowed when the operation doesn’t admit the caller’s kind of client, such as a machine client calling a user-only operation; and insufficient-role when a confirmed member’s roles don’t allow the action. Any other refusal is the generic forbidden, which says nothing more. Another organization’s resource is 404 (not-found) instead, so no reason confirms that it exists. An owner or administrator who sent no proof of a second factor gets mfa-required. An operation that needs an active subscription, when the organization has none, returns subscription-inactive, or organization-suspended when Fedwright suspended the organization. A suspended organization also refuses invitations, new joins through an invitation, and role changes with organization-suspended. Creating or joining an organization without a verified email returns email-unverified, and accepting an invitation sent to another address returns invitation-email-mismatch. A request from Fedwright’s web or mobile app whose session acts for no organization yet returns organization-required, except on getMe, createOrganization, and acceptInvitation. Shared data needs the acting organization’s own subscription; another organization’s doesn’t count.

Media typeapplication/problem+json

RFC 9457 Problem Details.

object
detail

An explanation specific to this occurrence of the problem. It is absent for server errors.

string
errors

Validation errors, by field path.

object
key
additional properties
Array<string>
instance

A URI reference that identifies this occurrence of the problem, when the API provides one.

string
limit

On rate-limited, the quota that is used up, such as org-day. On premium-quota-exceeded, org-premium-day. A 429 with an empty body does not name it.

string
resetsAt

On rate-limited from the API and on premium-quota-exceeded, when that quota starts again.

string format: date-time
status
required

The HTTP status code of the response, from 400 to 599.

integer
>= 400 <= 599
title
required

A short, human-readable summary of the problem. Use type to identify the kind of problem, not this text.

string
traceId
required

W3C trace ID of the failed request.

string
type
required

https://fedwright.com/problems/{code}, the same in every environment.

string format: uri

Example generated

{
"detail": "example",
"errors": {
"additionalProperty": [
"example"
]
},
"instance": "example",
"limit": "example",
"resetsAt": "2026-04-15T12:00:00Z",
"status": 1,
"title": "example",
"traceId": "example",
"type": "https://example.com"
}

The resource does not exist, or belongs to another organization.

Media typeapplication/problem+json

RFC 9457 Problem Details.

object
detail

An explanation specific to this occurrence of the problem. It is absent for server errors.

string
errors

Validation errors, by field path.

object
key
additional properties
Array<string>
instance

A URI reference that identifies this occurrence of the problem, when the API provides one.

string
limit

On rate-limited, the quota that is used up, such as org-day. On premium-quota-exceeded, org-premium-day. A 429 with an empty body does not name it.

string
resetsAt

On rate-limited from the API and on premium-quota-exceeded, when that quota starts again.

string format: date-time
status
required

The HTTP status code of the response, from 400 to 599.

integer
>= 400 <= 599
title
required

A short, human-readable summary of the problem. Use type to identify the kind of problem, not this text.

string
traceId
required

W3C trace ID of the failed request.

string
type
required

https://fedwright.com/problems/{code}, the same in every environment.

string format: uri

Example generated

{
"detail": "example",
"errors": {
"additionalProperty": [
"example"
]
},
"instance": "example",
"limit": "example",
"resetsAt": "2026-04-15T12:00:00Z",
"status": 1,
"title": "example",
"traceId": "example",
"type": "https://example.com"
}

A rate limit is used up. Direct API access counts against the organization’s burst, daily, and monthly quotas, which all its machine clients share; there is no client quota. A 429 comes in two shapes with two header families, so read the response by what it carries.

A machine client’s calls can get a 429 with an empty body. It has three headers: X-RateLimit-Limit, the exhausted rule’s limit and window, such as 3000, 3000;w=2592000; X-RateLimit-Remaining, what is left in that window (0 on a 429); and X-RateLimit-Reset, the seconds until that window resets. It has no Retry-After, no RateLimit headers, no Problem, and does not name the quota. Read the quota from the window w:

w (seconds) Quota
60 Burst.
86400 Daily.
2592000 Monthly (the calendar month is reported as 30 days; X-RateLimit-Reset is exact).

Wait X-RateLimit-Reset seconds before you retry. Refused requests still count: a client that keeps calling after a 429 keeps adding to the counter, so it does not regain budget inside the window. A tier change takes effect within 10 minutes (checkout, the billing portal, and grants alike), once the client gets a new access token.

Every other 429 has a Problem and Retry-After. The Problem type is rate-limited for a used-up quota, or, for an ask, ai-budget-exceeded or premium-quota-exceeded (below). On rate-limited, Retry-After is the seconds until the used-up limit’s window resets, the latest if several are. The Problem also carries limit, the quota that is used up (the one that resets last, if several are), resetsAt, when it starts again, and traceId. RateLimit-Policy and RateLimit describe only the caller’s own limits, here and on every other response a limit applies to. A request refused with rate-limited uses no quota. One refused after that check, for its scope, role, subscription, or kind of client, still counts, so once a quota is used up such a request gets 429 instead of its 403. A request that fails authentication is not counted. An ask that the organization’s AI spending budget can’t cover returns ai-budget-exceeded instead, with Retry-After at the end of the budget’s window: that budget counts what asks cost, not how many there are. An ask that the organization’s premium units for the day can’t cover returns premium-quota-exceeded, with limit (org-premium-day), resetsAt (the next midnight UTC), and Retry-After. That is the plan’s allowance: a plan with no premium units never covers an ask, and a higher plan may. A refused ask uses no units.

Media typeapplication/problem+json

RFC 9457 Problem Details.

object
detail

An explanation specific to this occurrence of the problem. It is absent for server errors.

string
errors

Validation errors, by field path.

object
key
additional properties
Array<string>
instance

A URI reference that identifies this occurrence of the problem, when the API provides one.

string
limit

On rate-limited, the quota that is used up, such as org-day. On premium-quota-exceeded, org-premium-day. A 429 with an empty body does not name it.

string
resetsAt

On rate-limited from the API and on premium-quota-exceeded, when that quota starts again.

string format: date-time
status
required

The HTTP status code of the response, from 400 to 599.

integer
>= 400 <= 599
title
required

A short, human-readable summary of the problem. Use type to identify the kind of problem, not this text.

string
traceId
required

W3C trace ID of the failed request.

string
type
required

https://fedwright.com/problems/{code}, the same in every environment.

string format: uri

Examples

An ask the premium units can't cover

{
"limit": "org-premium-day",
"resetsAt": "2026-11-08T00:00:00Z",
"status": 429,
"title": "Too Many Requests",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"type": "https://fedwright.com/problems/premium-quota-exceeded"
}
RateLimit
string

For each limit in RateLimit-Policy, the requests r its window still allows and the seconds t until the window resets, such as "org-burst";r=7;t=40, "org-day";r=62;t=41000.

RateLimit-Policy
string

The caller’s rate limits, in the IETF HTTPAPI RateLimit header fields: each limit’s name, its quota q, and its window w in seconds, such as "org-burst";q=20;w=60, "org-day";q=100;w=86400.

Retry-After
integer

Seconds to wait before retrying.

X-RateLimit-Limit
string

On a 429 with an empty body (and on the successful responses that carry these headers), the exhausted rule’s limit and window w in seconds, such as 3000, 3000;w=2592000. The window names the quota: 60 is burst, 86400 is daily, and 2592000 is monthly.

X-RateLimit-Remaining
integer

What is left in that rule’s window (0 on a 429).

X-RateLimit-Reset
integer

Seconds until that rule’s window resets.

Any other error. A request body over 64 KiB returns 413 (payload-too-large), and a request that takes longer than 10 seconds (90 for askOpportunities) returns 503 (request-timeout). An invitation when the API can’t send mail returns 503 (mail-unavailable), and creates nothing. An unexpected failure returns 500 (internal-error) with only type, title, status, and traceId.

Media typeapplication/problem+json

RFC 9457 Problem Details.

object
detail

An explanation specific to this occurrence of the problem. It is absent for server errors.

string
errors

Validation errors, by field path.

object
key
additional properties
Array<string>
instance

A URI reference that identifies this occurrence of the problem, when the API provides one.

string
limit

On rate-limited, the quota that is used up, such as org-day. On premium-quota-exceeded, org-premium-day. A 429 with an empty body does not name it.

string
resetsAt

On rate-limited from the API and on premium-quota-exceeded, when that quota starts again.

string format: date-time
status
required

The HTTP status code of the response, from 400 to 599.

integer
>= 400 <= 599
title
required

A short, human-readable summary of the problem. Use type to identify the kind of problem, not this text.

string
traceId
required

W3C trace ID of the failed request.

string
type
required

https://fedwright.com/problems/{code}, the same in every environment.

string format: uri

Example generated

{
"detail": "example",
"errors": {
"additionalProperty": [
"example"
]
},
"instance": "example",
"limit": "example",
"resetsAt": "2026-04-15T12:00:00Z",
"status": 1,
"title": "example",
"traceId": "example",
"type": "https://example.com"
}