Exercise 3 of 3 — The Big One: Contractors Cloud Job Log Middleware
This is the graduation exercise. You're going to build a middleware application that connects to Contractors Cloud, authenticates with an API token, reads your projects back out, and writes a job log note onto one of them through the API. If you finish this, you can build anything.
- Claude Code installed and working from Page 1: Setup & Foundation —
claude --versionprints a version number - Python 3.11 or newer — free, and Section 2 below walks you through installing it
- A Contractors Cloud login with permission to create an API access token — Section 3 below walks you through getting one, including what to ask for if you can't see the screen
What is an API?
An API (Application Programming Interface) is a set of rules for how software talks to other software. When you log into your bank's website, you're using a user interface (UI) — buttons, forms, visual stuff. An API is like a UI for software. Your code sends a structured request ("add this note to the Elm Street job") and Contractors Cloud processes it, just as if someone had typed it in manually.
What is a bearer token?
A bearer token is how Contractors Cloud knows your app is allowed into the account. Think of it like a valet key — you give the valet a special key that starts the car but won't open the trunk. The token gives your app access without sharing your password, and it carries only the permissions of the user who created it. Your code sends it on every single request, in a header that reads Authorization: Bearer YOUR_TOKEN. That is the whole handshake.
There is no sandbox. Read that twice.
QuickBooks gives developers a fake company full of fake data to practice on. Contractors Cloud does not. There is one environment and it is the live one. Everything you do in this exercise happens in your real account, against your real jobs, and your team will see it.
That is not a reason to skip the exercise. It is a reason to work in a specific order, and this exercise is built in that order:
- Read first, and read a lot. Every
GETrequest is harmless. You cannot break anything by asking questions. Spend your mistakes here. - Write exactly once, and write something additive. The one write in this exercise adds a note to a project. It does not change a contract, a price, a milestone, or a customer record.
- Label the write so a human knows what it is. Your test note will start with the words
API TEST, so if a project manager sees it, they know it isn't a real job instruction. - Clean up after yourself, and expect that to be the hard part. Section 8 covers why deleting is not always allowed.
What a real build looks like:
We have built a full document processing middleware for a client. It ingests PDFs, parses line items, maps them to records in the client's system, learns vendor patterns over time, validates everything, then pushes finished records in. It handles delivery documents, freight calculations, pricing lookups, and drift detection.
Yours does one thing: push a job note. That's the seed. Everything else grows from it.
The middleware runs on Python using Flask (a lightweight web framework). The exercises so far were just HTML files — this one is a real application with a server.
- Close every Command Prompt window and open a brand new one. A window opened before the install can't see it.
- Start Claude Code in that new window and say: "python --version isn't working after I installed Python. Fix my PATH." This is exactly the kind of thing it sorts out for you.
To talk to Contractors Cloud through the API, you need an API access token. You create it inside the Contractors Cloud web app, on your own account. It is free and it takes about a minute, assuming your user has the permission to do it.
Create the token:
.env file you'll create in Section 4. If you lose it, you don't recover it — you delete that token and create a new one, which costs you nothing.
- Anyone holding it can do everything your user account can do, with no password and no second factor.
- It goes in your local
.envfile and nowhere else. Not in the code. Not in a comment. Not in a screenshot. - Never paste it into a chat window. Not into Claude Code, not into email, not into a text message, not into a support ticket. Claude does not need to see your token to write code that uses it — the code reads it from the file at run time. This is a rule for every API key you will ever handle, and this is the exercise where you build the habit.
- If you think it leaked, delete it on that same Integrations screen and make a new one. Takes a minute, ends the problem.
If you can't find that screen:
Creating API tokens is a permissioned action, so a limited user may not see it at all. If the menu isn't there, don't go hunting through settings. Ask your Contractors Cloud administrator, or Contractors Cloud support, for exactly this:
Find your company id:
Contractors Cloud organizes a lot of the API around companies. Several URLs you'll use have a numeric company id sitting in the middle of them, like /companies/YOUR_COMPANY_ID/leads. You need that number.
The good news is you do not have to go find it in the web app. Once your token works, the API will tell you: the record for the logged-in user carries a default company on it. Section 6 makes that call. For now, just know that the number exists and that you'll have it in a few minutes.
• An API access token
• The base URL: https://api.contractorscloud.com/api/v1
• A company id (or a plan to get one in Section 6)
Keep the token in a file on your own machine. You will paste it into
.env yourself in the next section, by hand, and you will not show it to Claude.
First, have Claude set up the project structure. Notice what this prompt does not contain: your token. It tells Claude to write a .env file with a placeholder in it, and you will replace the placeholder yourself, in Notepad, in a moment.
Now put your token in, by hand:
CC_COMPANY_ID as the placeholder for now if you don't have the number yet. Section 6 gets it for you, and you'll come back and fill it in then.
Now for the big build. This prompt tells Claude to create the entire Flask application. It's longer than previous prompts because there's more to build, but the pattern is the same — describe what you want, Claude builds it.
pip install), say yes.
python app.py. You should see something like * Running on http://127.0.0.1:5000 in the output.
The server is running. Now let's connect it to your Contractors Cloud account. This happens in two calls, and it's worth understanding why there are two.
GET /meta/status needs no token at all — it just proves your machine can reach the API and that the API is online. GET /users/me needs your token, and answers a different question: who am I connected as. When a connection fails, knowing which of those two broke tells you immediately whether the problem is your network or your key. Split your diagnostics like this in everything you build.
.env, because the values are read when the app starts. Press Ctrl+C in the server window and run python app.py again, or just ask Claude to restart it.
Now read some real data back.
Stop and look at that for a second. That table was not typed by anyone. Your code asked Contractors Cloud a question and Contractors Cloud answered it. That is the read half of middleware, and it is already useful on its own — every dashboard, every report, every export you ever build starts exactly here.
• 401 Unauthorized — the token is wrong, expired, or was pasted with a stray space or line break. Open
.env and check that the value sits on one line with nothing after it.• 403 Forbidden — the token is valid but the user it belongs to lacks permission for that endpoint. This is an account permissions question for your administrator, not a code bug.
• An HTML page instead of JSON — you forgot the
Accept: application/json header. The API will hand you a web page if you don't ask for data.• 404 on a path you were sure about — see Section 8. This one has a specific trap in it.
The moment of truth. You're going to create a record in Contractors Cloud through your middleware.
Now verify it actually made it in. Not in your app — in the CRM:
Think about what happened: you typed information into YOUR app, your app talked to Contractors Cloud's servers through the API, and a note appeared on a job. No manual data entry. No copy-pasting. Software talking to software.
This is what middleware does. This is the pattern for everything.
Now make a mistake on purpose.
You typed API TEST and whatever came after it. Suppose you got that wording wrong and want to fix it. The obvious move is to update the note. Try it, and watch what happens:
200 OK. Your request succeeded. And your edit did not happen. A note's body is immutable once created: the endpoint accepts your request, updates the couple of fields it is willing to update, and silently discards the body you sent. No error. No warning. Just a green light and a lie.A 200 means "I understood your request." It does not mean "I did what you meant." If you had trusted that status code, your app would have shown a happy confirmation screen while the wrong text sat on a live job.
Clean up:
API TEST on the front, which is exactly why you labeled it that way in the first place. Now you also know something real about your own account's permissions, which you would not have learned from reading documentation.
You've made a handful of calls and they mostly worked. Real integration work is the other days. Every item below is a thing that actually happens on this API, costs an afternoon the first time, and costs ten seconds once you've seen it. Read them now so you recognize them later.
1. A 404 does not always mean the thing is gone.
It can also mean you invented a path. The documentation talks about lead sources, so /lead-sources feels like an obvious guess. It returns 404. The real path hangs the collection off the company:
The lesson is not "memorize this path." It is that a 404 from an API you're new to means "check the URL" far more often than it means "no such record." Look up the actual endpoint before you assume the data isn't there.
2. A 200 does not always mean it saved.
You proved this one yourself in Section 7. A note body is immutable; the PUT returns 200 and drops the field. The general rule that comes out of it: after any write that matters, read it back and compare. Your app should verify against the server's copy, not against the value it sent. That one habit will save you more grief than any other thing on this page.
3. A 400 for a filter that looks perfectly reasonable.
Filters are per endpoint, not global. filter[company_id] works in plenty of places, and on /accounts it returns 400. Nothing about your syntax is wrong; that endpoint simply doesn't offer that filter. Check the endpoint's own list of supported filters rather than assuming a filter that worked next door works here.
4. A 422 because a field is longer than you thought.
Several fields have length caps that are easy to blow past with real-world data:
address_street— 50 characterstitle— 50 charactersphone_work— 14 characters, which a formatted number with an extension exceeds immediately
They're in the API's schema, and they are exactly the kind of detail nobody reads until a batch import dies on record 340. If you're pushing data in from a spreadsheet, validate lengths on your side first and decide deliberately what to do with the overflow, rather than letting the API decide by rejecting it.
5. Some things the API will not do at all.
Custom field values can be read and set through the API. Custom field definitions cannot be created through it — those are configured in the web app only. If your plan depends on your middleware standing up new fields automatically, the plan needs changing. Find the walls early; they're cheaper to discover in design than in the middle of a build.
6. Side effects you didn't ask for.
Creating a project does more than create a project. It writes a system note into the activity feed, it fires a webhook, it geocodes the address if you didn't supply coordinates, and in an account with CompanyCam connected it spawns a matching place over there too. None of that is a defect. It is what "create a project" means in this system. Before you automate a write, ask what else that write sets in motion — and remember there's no sandbox in which to find out gently.
7. There is a rate limit, and you should stay well under it.
The ceiling sits around 300 requests per minute. A loop over a few hundred jobs will hit that without trying. Put a small deliberate pause between requests (a quarter of a second is plenty), and cache what you've already fetched so a re-run doesn't re-ask. Your build prompt in Section 5 already asked Claude for both.
Update your memory log with everything you accomplished:
Ctrl+C. To start it again later, navigate to the folder and run python app.py.
You've built three things of increasing complexity:
- A standalone tool (Eisenhower Matrix) — HTML only, no backend
- A connected form (Homework Email) — HTML + Google Apps Script backend
- A real middleware (Job Log Importer) — Python + Flask + a bearer token + the Contractors Cloud API
The pattern is always the same:
- Create a project folder with CLAUDE.md and a context/ folder (PROJECT.md, JOURNAL.md)
- Describe what you want to Claude
- Review the plan, approve it, let Claude build it
- Test, customize, iterate
- Update context/JOURNAL.md before closing
Ideas for what to build next:
- Turn the projects page into a real dashboard — jobs by milestone, by rep, by month, with the numbers you actually manage to
- Pull material orders and contracts and put revenue and material cost side by side on one screen
- Push job updates in from wherever they start — a form your crews fill out on a phone, a shared spreadsheet, an email inbox
- Export a weekly report to a spreadsheet on a schedule, so nobody has to remember to run it
- Connect to other APIs — Google, a supplier portal, an accounting system, whatever else your business runs on
Every one of these follows the same pattern you just learned. The only difference is which API you're talking to and what the UI looks like. Claude handles the technical details. You handle the vision.