Your first API call in 5 minutes.
Four steps, all curl: get a workspace, get a token, list your projects, and create an RFI that shows up in the app for your team.
Get a workspace
Start a free trial, or sign in if your company already uses os.construction. You need at least one project you're a member of.
Note your company ID. It's the number after /companies/ in the app's address bar, for example app.os.construction/companies/1/….
Use your own login
The API acts as the user whose credentials you send, with that user's permissions. Building an integration that runs unattended? Make a separate user for it later, with a narrow permission template.
Get a token
Trade your email and password for an access token at /oauth/token. It lasts two hours. If you use an authenticator app, add -d mfa_code=123456.
Leave scope out for this walkthrough: the portfolio call in step 3 reads across several tools. Scopes narrow a token once you know which tools you need.
curl -X POST https://app.os.construction/oauth/token \
-d grant_type=password \
-d username=you@yourcompany.com \
-d password='your-password'
# {"access_token":"d534c4da…","token_type":"Bearer","expires_in":7200,
# "refresh_token":"3504e4e2…","created_at":1792502999}
export OS_ACCESS_TOKEN=d534c4da… # the access_token value
export OS_COMPANY_ID=1 # from the app's address barList your projects
The portfolio endpoint returns every project you can see, with open-item counts and budget rollups. Pick a project_id.
Every request carries the same two headers: Authorization with your token, and OS-Company-Id.
curl https://app.os.construction/rest/v1.0/companies/$OS_COMPANY_ID/portfolio \
-H "Authorization: Bearer $OS_ACCESS_TOKEN" \
-H "OS-Company-Id: $OS_COMPANY_ID"{
"projects": [
{
"project_id": 2,
"name": "2001 - Harbor View Medical Center",
"project_number": "2001",
"stage": "Course of Construction",
"active": true,
"open_items": { "rfis": 0, "submittals": 0, "punch": 0, "observations": 0, "overdue": 0 },
"financials": { "partial": false, "original_budget": 0, "committed": 0, "change_orders": 0, "projected": 0 }
}
],
"totals": { "projects": 1, "active_projects": 1, … }
}Create an RFI
Request bodies are wrapped in the resource name, here rfi. subject and question.body are required. Leave one out and you get a 422 that names the field: {"errors":{"rfi.subject":["can't be blank"]}}.
The RFI gets the next number on the project and lands in the RFI log in the app, with you as the creator and ball in court.
curl -i "https://app.os.construction/rest/v1.0/projects/2/rfis?per_page=25" \
-H "Authorization: Bearer $OS_ACCESS_TOKEN"
# Total: 1 Per-Page: 25 Link: <…>; rel="first", <…>; rel="last"curl -X POST https://app.os.construction/rest/v1.0/projects/2/rfis \
-H "Authorization: Bearer $OS_ACCESS_TOKEN" \
-H "OS-Company-Id: $OS_COMPANY_ID" \
-H "Content-Type: application/json" \
-d '{
"rfi": {
"subject": "Fire dampers missing at level 3 corridor",
"question": {
"body": "The mechanical drawings show fire dampers at the level 3 corridor wall, but none were delivered. Please confirm type and location."
},
"due_date": "2026-10-27"
}
}'HTTP/1.1 201 Created
{
"id": 1,
"number": "1",
"subject": "Fire dampers missing at level 3 corridor",
"status": "open",
"due_date": "2026-10-27",
"created_by": { "id": 1, "login": "you@yourcompany.com", "name": "Charley Forey" },
"ball_in_court": { "id": 1, "login": "you@yourcompany.com", "name": "Charley Forey" },
"questions": [
{ "id": 1, "plain_text_body": "The mechanical drawings show fire dampers…", "answers": [] }
],
"created_at": "2026-10-20T13:30:29.192Z",
…
}Where to go from here
Authentication
Refresh before the two hours run out, add scopes, revoke.
Open →Limits, paging & errors
Walk long lists with the Link header and handle 422s.
Open →RFI endpoints
Answer, forward, close and export RFIs.
Open →Webhooks
Subscribe an endpoint and send yourself a signed test.
Open →Responses on this page come from a local run of the app against its sample data.