← Back to Exercise 2 of 3

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.

Before you start — what this exercise assumes: This is the Contractors Cloud version of Exercise 3. If your books live in QuickBooks Online instead, take the QuickBooks version. Same exercise, different system.
0 / 0 steps
1
What Is Middleware & Why Build It?
Middleware is software that sits between two systems and makes them talk to each other. You have Contractors Cloud. You also have information that lives outside it — a crew's texts from the roof, a supplier's delivery confirmation, a spreadsheet somebody keeps. Right now, someone retypes that into the CRM by hand. Your middleware automates that — it takes the job information and pushes it into Contractors Cloud through the API.

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.

This is simpler than the QuickBooks version of this exercise on purpose. QuickBooks makes you walk an OAuth dance: redirect the user to Intuit, catch them coming back, trade a code for a token, refresh the token when it expires. Contractors Cloud skips all of that. You create a token on a settings screen, you put it in a file, you send it on every request. Less to build. Also less to hide behind — a bearer token is a naked key, so how you store it matters more, which is Section 3.

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 GET request 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.

2
Install Python

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.

Download Python
That button opens the Windows downloads page at python.org in a new tab. Python is free. Take the latest stable release's Windows installer (64-bit).
Note: Some installers offer an "Add python.exe to PATH" checkbox, but it moves around between versions and is easy to miss. Don't hunt for it — Claude will handle it in the next step.
On a company-managed computer: "Install Now" puts Python in your own user folder, no administrator rights needed, so it usually works even on locked-down machines. If your IT security policy blocks it anyway, ask IT to install Python 3.11 or newer.
Verify it worked — open a NEW Command Prompt (close any old ones first; Start menu → type cmd → press Enter) and type:
python --version
If it says "'python' is not recognized", or the Microsoft Store pops open — Windows can't find Python yet. Two things to try, in order:
  1. Close every Command Prompt window and open a brand new one. A window opened before the install can't see it.
  2. 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.
If the Microsoft Store opens instead of printing a version, that's a Windows placeholder, not Python — close the Store and re-run the installer from python.org.
If Python was already installed, skip this section. You just need version 3.11+.
3
Get Your Contractors Cloud API Token

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.

Heads up on the app layout: Contractors Cloud moves settings screens around between releases, so the exact menu names below may not match what you see. The concepts don't change — you're creating an API access token, copying it once, and finding your company id. Hunt for those ideas, and if a label looks different, tell Claude Code what's on your screen and it'll orient you.

Create the token:

The token is shown once and never again. This is normal and it is deliberate. Copy it the moment it appears and paste it straight into the .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.
Read this before you go any further. Your token is a naked key.
  • Anyone holding it can do everything your user account can do, with no password and no second factor.
  • It goes in your local .env file 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:

What to ask for I'd like to use the Contractors Cloud REST API (api.contractorscloud.com/api/v1). Two things please: 1. Enable API access for my user and grant me permission to create an API access token on the Integrations > API Access Tokens screen. If that isn't something my user can have, please create a token for me instead and send it to me securely. 2. Tell me the numeric company id for our company, the one that goes in /companies/{id}/ URLs. I'm building a small internal read-and-write tool. Read only to start.

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.

You now have everything you need from Contractors Cloud:
• 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.
4
Set Up the Project
cd C:\dev\JobLogImporter
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.

Prompt: Set up the middleware project Set up this project. Create: 1. CLAUDE.md (at the project root) with project rules: Flask app on localhost:5000. Contractors Cloud has NO sandbox, so every call hits the live account. Reads are unrestricted; the only write allowed in this project is adding a note to a project. Never commit .env or secrets to git. Never print the API token to the console or into a log file. 2. context/PROJECT.md - We're building a simple Contractors Cloud job log middleware. Flask app on localhost:5000. Authenticates with a bearer token. Lists projects from the CRM and lets me post a note onto one of them. 3. context/JOURNAL.md - Starting project. Setting up workspace. 4. A .env file with EXACTLY these lines and these placeholder values, unchanged (DO NOT commit this to git, and do not ask me for the real token, I will fill it in myself): CC_API_TOKEN=YOUR_TOKEN_GOES_HERE CC_BASE_URL=https://api.contractorscloud.com/api/v1 CC_COMPANY_ID=YOUR_COMPANY_ID 5. A .gitignore that excludes .env files 6. A .env.example that is a copy of .env, safe to commit, so I remember which variables exist. Do not ask me for my API token at any point. The code must read it from .env at run time.

Now put your token in, by hand:

Leave 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.
Your .env file contains a live key to your CRM. The .gitignore ensures it won't get uploaded if you ever use Git. But also: don't copy your .env file to other computers, don't email it, don't put it in a shared folder, and don't paste its contents into any chat or forum. If Claude ever offers to print the file for you, tell it no.
5
Build the Middleware

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.

Prompt: Build the job log importer Build a Flask application that talks to the Contractors Cloud API. Here's what I need: ARCHITECTURE: - app.py as the main Flask app (port 5000) - Load CC_API_TOKEN, CC_BASE_URL and CC_COMPANY_ID from .env using python-dotenv. Never print the token. - Write a small cc_client.py module of your own. Do not install a third party Contractors Cloud package. Every request sends these headers: Authorization: Bearer <token from .env> Accept: application/json Content-Type: application/json - The client must return BOTH the HTTP status code and the response body to the caller, and must not raise on a 4xx. I want to see the status code in the UI. - Rate limit yourself: at least 0.25 seconds between requests, so we stay under the published ceiling. - List endpoints are paginated. Handle page[number] and page[size] and follow the pages until they run out. PAGES: 1. Home page (/) - Dashboard showing: - Connection status, which you determine by calling GET /meta/status and then GET /users/me - The name of the authenticated user and the id of their default company, pulled from /users/me - A link to the projects page 2. Projects page (/projects) - A table of projects pulled live from GET /projects, showing at minimum the project id, number, name and city. Each row links to the note form for that project. 3. Note form (/projects/<id>/note) - A form with: - The project id and name shown read only at the top - A note body textarea - A "Post note to Contractors Cloud" submit button - It POSTs to /projects/<id>/notes 4. Result page - After posting, show the HTTP status code, the new note's id, and the note body EXACTLY AS THE API RETURNED IT, read back with a follow up GET. Do not show me what I typed. Show me what the server actually stored. REQUIREMENTS: - Install the Python packages needed (flask, python-dotenv, requests) - Create a requirements.txt - Clean, professional UI (use Bootstrap 5 for styling) - Error handling: when a call returns 4xx, show the status code and the API's own error message on the page. Do not swallow errors and do not fake success. - Show clear status messages throughout the process Keep it simple. No PDF upload, no parsing, no syncing. Just: read projects, post one note, read it back. The simplest possible working middleware. After building, start the Flask server so I can test.
This build takes longer than the previous exercises. Claude is creating multiple files (app.py, cc_client.py, templates, requirements.txt), installing Python packages, and possibly setting up the Flask server. Let it work. If it asks you to approve running commands (like pip install), say yes.
If Claude didn't start the server, ask it: "Start the Flask server for me." Or you can ask Claude to run python app.py. You should see something like * Running on http://127.0.0.1:5000 in the output.
Before you click anything, check one thing. Ask Claude: "Show me every place in this project where the token is used, and confirm it is never written to a log, a template, or the console." Reading the answer is part of the exercise. A middleware that leaks its own key into a log file is a middleware you cannot deploy.
6
Connect to Contractors Cloud

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.

Call one asks "is the API up?" Call two asks "does my key work?" 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.
That's the whole handshake. No redirects, no authorization screen, no token refresh. Your app sent a header and the API answered with your identity. Everything else you ever do against this API is that same request with a different URL on the end of it.
You'll need to restart the Flask server after editing .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.

If the connection failed, go back to Claude Code and tell it what happened. Paste the status code and any error message (paste the error, never the token). Common issues:
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.
7
Write Your First Record

The moment of truth. You're going to create a record in Contractors Cloud through your middleware.

This one is real. There is no sandbox, so this note lands on a live job and your team can see it in the activity feed. Pick your project deliberately: an old job, a completed job, or one of your own. Do not pick a job a crew is working today.

Now verify it actually made it in. Not in your app — in the CRM:

You just pushed data into your CRM through the API.

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:

Prompt: Try to edit the note Add a temporary route to the app that lets me edit the note I just created. It should send: PUT /projects/<project_id>/notes/<note_id> with a JSON body containing a new "body" value. Then, in the same request cycle, do a GET on that same note and show me BOTH the status code from the PUT and the body field that came back from the GET. Do not tell me it worked based on the status code. Show me the note as the server has it.
That is the single most important thing on this page. The API returned 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.
So how do you correct a note? You don't. You replace it. POST a new note with the corrected text, confirm it landed, then DELETE the old one. Two writes and a read instead of one write, because the system's rules say so rather than because you'd have designed it that way. Half of integration work is finding out what the other system will and will not let you do, and then building around it honestly.

Clean up:

If the delete comes back 403, that is not a bug. Deleting a note requires elevated permissions in your organization, and your user may not have them. Two honest options: ask an administrator to remove it, or leave it. It says 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.
8
When the API Fights Back — The Traps

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:

GET /companies/YOUR_COMPANY_ID/leads

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 characters
  • title — 50 characters
  • phone_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.

The one rule underneath all seven: the API's response is a claim, not a receipt. Verify by reading back. Every serious integration you build will have a verification step, and the ones that don't are the ones that quietly corrupt data for six weeks before anyone notices.
9
Save Progress & Wrap Up

Update your memory log with everything you accomplished:

Update context/JOURNAL.md with everything we did. Include the Contractors Cloud connection status, what the app looks like, what we tested, which traps we hit, and ideas for next features. Do not write the token into the journal.
To stop the Flask server: Go to the Command Prompt window where the server is running and press Ctrl+C. To start it again later, navigate to the folder and run python app.py.
One housekeeping item, whenever you're finished experimenting. If you built this token just to learn on, go back to Integrations → API Access Tokens and delete it. An unused live key sitting in a folder is the kind of thing you want to retire on purpose rather than forget about. Making a new one later takes a minute.
10
Where to Go From Here

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:

  1. Create a project folder with CLAUDE.md and a context/ folder (PROJECT.md, JOURNAL.md)
  2. Describe what you want to Claude
  3. Review the plan, approve it, let Claude build it
  4. Test, customize, iterate
  5. 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.

You Made It.

Three exercises. Three levels of complexity. One more page — the habits and harness settings that separate fighting the tool from cooperating with it.

On to Harness & Memory →



Back to the beginning