Turn signed Statements of Work into staffed projects with the Operating API
Learn how to use the Operating API to automatically turn a signed Statement of Work into a staffed project — creating the client, timeline, budget, and open roles ready for your staffing lead to fill, without manual data entry.
Written By Lauri Eurén
Last updated About 8 hours ago
This guide walks through building a pipeline that turns a signed Statement of Work into a fully shaped project in Operating — client, timeline, budget, and a set of open roles ready for your staffing lead to fill — all created automatically through the public API. If you’d like to read a more generic article on the topic, navigate to our blog!
When a Statement of Work is signed, someone usually has to read it and recreate it by hand in your operations tool: set up the client, the project, the timeline, the open roles the engagement needs, and the budget. This guide shows how to do that automatically. You read the document once, a person checks the result, and the API writes everything into Operating in the right order — leaving your staffing lead to assign the actual people.
This is a pattern several technical firms have built. None of it relies on private endpoints — every call below is in the public API, documented at operating.readme.io.
What you'll build
A short pipeline that takes a contract in one end and produces a project full of open, ready-to-fill roles at the other:
Capture the SOW when it arrives
Extract the fields that matter
Review the extracted data with a person before writing anything
Write the project and its open roles into Operating through the API
Hand off to your staffing lead, who assigns people in Operating
Steps 1–3 are yours to build with whatever tools you like. Step 4 is where the Operating API does the work, and it's the part this guide covers in detail.
Where the automation stops and staffing begins
This is the important design decision, so it's worth stating up front. In Operating, the work splits cleanly in two:
Defining the staffing need — creating the project, its timeline, and an open position for each role, with an allocation that says how much of that role is needed and when. This is structured data that comes straight out of the SOW, so it's the part you automate.
Finding the right people — deciding who actually fills each open role. This depends on availability, skills, and judgment that lives with your staffing lead, not in the contract. This part stays a human job, done in Operating.
So the automation deliberately stops short of assigning anyone. It creates open positions (roles with no person attached) and the time demand against them. Your staffing lead then opens the project and sees a fully shaped set of roles to fill, instead of a blank project or a PDF to read. They can then staff the project “manually” or using the AI chat (LLM) via MCP.
Before you start
Get an API key. Each workspace has its own key. Treat it like a password.
Base URL:
https://api.operating.app/v1Response shape: every response is
{ "data": {...}, "meta": {...} }. Thedata.idreturned from each create call is what you pass into the next call.
A note on number formats
Two fields aren't plain numbers, so convert your extracted values before sending:
Money (like a budget amount) is an object with a currency, in normal units.
{ "amount": 150000, "currency": "USD" }means 150,000 USD. So "$150,000" from a contract becomes that object.Allocation percentage is expressed in ten-thousandths of a percent: 100% becomes
1000000, and 50% becomes500000.
Step 1 — Capture the SOW
Signed contracts usually arrive as a PDF, often from a signing tool. Two common ways to catch them:
Watch a shared inbox for emails whose subject contains "Statement of Work" or "SOW", and grab the PDF attachment.
Listen to your signing tool directly, if it has its own API or webhook. This is cleaner because you skip the inbox entirely.
Either way, the output of this step is a PDF and a trigger that something new has arrived. You can also simply have a button that’s pressed when you want to fetch new SoWs from the source system.
Step 2 — Extract the fields
Parse the PDF and pull out the fields you'll need to build the project. At a minimum:
Client / customer name
Project name
Start and end dates
Billing schedule and milestones (for example, 50% upfront, then 25% / 25%)
Contract value
The roles and time commitments the engagement calls for (for example, "1.0 FTE designer, months 1–3") — note the role, not a named person
Use whatever document parsing and extraction approach you prefer. Keep a reference to where in the document each field came from — you'll use it in the next step.
Step 3 — Review before writing
Show the extracted fields to a person, next to the source reference, before anything is written to Operating. Let them correct a date or a number and approve the result.
Keep this manual on purpose, at least to begin with. It catches the contracts that are worded differently from the rest, and it builds confidence that the data landing in your systems is right. The goal is to remove the typing, not the judgment.
Step 4 — Write the project and its open roles into Operating
The API mirrors the objects you'd otherwise create by hand. Call them in dependency order, carrying the id from each response into the next call.
First, look up the reference data you'll need (these are read-only GETs):
You don't need to look up people here — that's the staffing lead's job later.
Then create the project's pieces:
1. Create the client
POST /v1/clients { "name": "Acme Corp", "invoicingEmail": "ap@acme.com", "uniqueIdentifier": "acme-corp" }Only name is required. Save the returned data.id as clientId. If the client might already exist, list clients first and reuse the existing id.
2. Create the project
POST /v1/projects { "name": "Acme – Website Rebuild", "clientId": 1234, "from": "2026-07-01", "through": "2026-09-30", "billingType": "fixed-price" }name is the only required field; everything else is optional. Valid billingType values are time-and-materials, capped-time-and-materials, fixed-price, and non-billable. Save the returned projectId. Linke the project with the client you created.
NOTE: if your CRM integration has already created a client and project, poll them first, and don’t try to create duplicate projects.
3. Lay out the timeline as phases (optional)
Create one phase per milestone or stage in the SOW. Phases need a projectId and a name:
POST /v1/phases { "projectId": 5678, "name": "Phase 1 – Discovery", "fromDate": "2026-07-01", "throughDate": "2026-07-31" }4. Set the budget
Budgets require projectId, name, amount, from, and through. Note amount is a money object with a currency:
POST /v1/budgets { "projectId": 5678, "name": "Contract value", "amount": { "amount": 150000, "currency": "USD" }, "from": "2026-07-01", "through": "2026-09-30" }5. Add an open position for each role the SOW calls for
A position is a role slot on the project. For this flow, create it without an assigned person — that leaves the role open for staffing. Give it a projectId, and optionally attach it to a phase and describe the role with a competence role and seniority:
POST /v1/positions { "projectId": 5678, "phaseId": 9012, "competenceRoleId": 3, "competenceRoleSeniorityId": 7 }Note there's no assignedPersonId — that's deliberate. The field exists if you want to assign someone, but leaving it out is what makes this an open role for the staffing lead to fill. Save the returned positionId.
6. Express the time demand as an allocation (still no person)
An allocation says how much of a role is needed, over what window — the capacity requirement the staffing lead will fill. It belongs to the open position and needs a positionId and a statusId. A tentative status is a good default here, since nothing is confirmed yet. Remember percentage is in ten-thousandths of a percent, so 100% is 1000000:
POST /v1/allocations { "positionId": 4567, "statusId": 1, "percentage": 1000000, "from": "2026-07-01", "through": "2026-09-30" }An SOW line like "1.0 FTE designer for months 1–3" becomes exactly this: an open designer position with a 100% allocation across the first three months. No person is named — the allocation describes the need, and your staffing lead decides who meets it.
Step 5 — Hand off to staffing
At this point the project is fully shaped: client, timeline, budget, and a set of open positions each with a clear time demand. Your staffing lead opens it in Operating and does the part that needs judgment — checking availability and skills and assigning the right person to each open position. They're filling in a structured plan rather than building one from a PDF.
If you also keep deals in Operating's CRM or push to an outside CRM or finance system, do that from the same approved dataset so every system tells the same story (POST /v1/crm/deals).
Getting extraction accuracy right
You don't need perfect extraction to save real time. A practical way to tune it:
Run your extractor against a batch of past contracts.
Compare its output to what was actually entered in Operating at the time — that's your ground truth.
Measure field-by-field accuracy and aim for roughly 70–90%, then adjust extraction where it's weakest.
Even at 70–80%, a staffing lead filling open roles on a pre-built project is far faster than one starting from a blank screen.
Make it safe to re-run
Two things keep the automation reliable:
Use external IDs. Client, project, and allocation create calls accept an
externalClients/externalProjects/externalAllocationsarray carrying your system's IDs. These must be unique, which lets you match records on re-run instead of creating duplicates if a job retries.Mind the order. Each object depends on the one before it (project needs a client, phase and position need a project, allocation needs a position). Carry each returned
idforward, and fail the whole batch cleanly if any step errors, so you never leave a half-built project behind.
Endpoints used in this guide
Full field-level reference for every endpoint is at operating.readme.io.