Control Third-Party API Access
Build a TypeScript API with partner scopes, quotas, audit logs, and alerts.
Introduction
30 Second Summary
A trusted partner can still ask for far more personal data than their job requires. When every request looks legitimate, abuse can hide in plain sight.
In this project, you will build a local TypeScript API that controls which records each partner can access. It refuses misuse even when every request carries valid credentials.
What You'll Build
From your terminal, you act as a trusted partner whose overreach the API stops in real time.
By the end of this project, you'll have:
- A working lookup endpoint that returns a sample person when the partner has permission to query that record.
- Partner population safeguards that reject out-of-scope searches. The same API stops requests once the grant reaches its daily quota.
- An audit trail that records every lookup. A volume check flags a grant when its activity jumps above its baseline.
- Secret Mission: An optional challenge to push your API security skills further.
Do I need anything before starting?
You need Node installed on your computer. You also need basic TypeScript familiarity plus access to curl.
Before We Start
Every protection in this project runs inside a local API. You need a consistent runtime before you can test whether a trusted partner stays inside its grant.
Node.js provides the local runtime for the API. TypeScript gives each request check a defined structure.
npm records the HTTP library plus the development tools in one project manifest.
In this step, get ready to:
- Verify the local Node.js runtime meets version 20 or later.
- Initialize the local npm project.
- Prepare the project to run a TypeScript HTTP API during development.
Verify the local runtime
Node.js runs the API on your computer. npm comes with Node.js so you can install each library the project needs.
- Press Cmd+Space on macOS or the Windows key on Windows to open system search.
- Type Terminal on macOS or PowerShell on Windows into the search field.
- Press Enter to open the selected terminal.
- Check the installed runtime by running these commands:
node --version
npm --version
What do these commands show?
- The first command prints the installed Node.js version.
- The second command confirms that the npm command is available.
- Match your terminal output to the scenario below.
✔️ I see version 20 or higher
Your local runtime meets the project requirement. npm is also ready to manage the project packages.
ⓧ I see an older version
The installed runtime is below the project minimum. Updating it gives the TypeScript tools a supported environment.
- Visit the official Node.js download page.
- Download the LTS release for your operating system.
- Complete the installer with its default options.
- Close the current terminal window after the installer finishes.
- Repeat the system search steps above to start a fresh terminal session.
- Check the updated versions by running these commands:
node --version
npm --version
What should I see?
The first line now shows Node.js version 20 or higher. The second line shows the installed npm version.
ⓧ Command not found
Your computer cannot find Node.js yet. Installing the LTS release also installs npm.
- Visit the official Node.js download page.
- Download the LTS release for your operating system.
- Complete the installer with its default options.
- Close the current terminal window after the installer finishes.
- Repeat the system search steps above to start a fresh terminal session.
- Confirm the installation by running these commands:
node --version
npm --version
What should I see?
The terminal now prints a Node.js version of 20 or higher. It also prints an npm version.
Still missing the runtime?
A terminal that stayed open during installation may still use the old system path. Start a fresh terminal session before checking again.
Confirm that the installer completed before closing it. Help me troubleshoot my Node.js installation.
Create the local project
An npm project stores its identity plus package list in package.json. Express supplies the HTTP server tools that the lookup endpoint uses.
- Create the trusted-partner-api folder on your Desktop by running these commands:
cd ~/Desktop
mkdir trusted-partner-api
cd trusted-partner-api
What do these commands do?
- The first command moves the terminal to your Desktop.
- The second command creates the trusted-partner-api folder.
- The final command moves the terminal into that new folder.
- Initialize the npm project with default values by running this command:
npm init -y
What does this command create?
npm creates package.json inside trusted-partner-api. The file becomes the manifest for the local API.
- Confirm the project manifest exists by running this command:
ls
What should I see?
The terminal output includes package.json. This confirms that npm initialized the project in the correct folder.
The API needs one runtime dependency. Its TypeScript runner plus type definitions belong in the development dependency list.
- Install the HTTP library plus the TypeScript development tools by running these commands:
npm install express
npm install --save-dev typescript @types/express @types/node tsx
What do these installs add?
- Express becomes a runtime dependency because the API needs it while serving requests.
- TypeScript checks the project code during development.
- The type packages describe the Node.js plus Express APIs to TypeScript.
- The tsx runner executes TypeScript locally.
- Inspect the recorded package groups by running this command:
npm pkg get dependencies devDependencies
What should I see?
The dependencies output includes Express. The development dependencies output includes TypeScript plus tsx.
You also see the Node.js plus Express type packages. Their version ranges may differ as npm installs current compatible releases.
Package installation not completing?
Confirm that your terminal is still inside the trusted-partner-api folder. A network interruption can also stop npm from downloading packages.
Run the install commands again after your connection recovers. Help me diagnose this npm installation.
Configure the development command
A development script gives the project one repeatable way to run the future API entry point. Watch mode restarts that process when its TypeScript files change.
- Add the development script to package.json by running this command:
npm pkg set scripts.dev="tsx watch src/server.ts"
What does this configuration do?
- The scripts.dev field stores the local development command.
- The command points the TypeScript runner at src/server.ts.
- Watch mode reloads the server process after a source file changes.
Before you check, what value do you expect npm to return for the development script?
- Read the saved development command by running this check:
npm pkg get scripts.dev
What should I see?
The terminal prints "tsx watch src/server.ts". This proves that package.json contains the development server command.
That is the local foundation in place. Your project can now install HTTP code plus run TypeScript through one npm script.
Development command missing?
Confirm that the configuration command contains scripts.dev. Keep the quotation marks around the complete script value.
Make sure the terminal remains inside trusted-partner-api. Help me fix this npm script.
✔️ Awesome, I've got everything!
Great. Your terminal is inside the initialized project folder with the API dependency plus local TypeScript tools installed.
ⓧ I'd like to double check the full setup
Use this checklist to compare your local project with the required end state.
- Confirm the terminal remains inside trusted-partner-api.
- Confirm package.json exists in that folder.
- Confirm Express appears under the runtime dependencies.
- Confirm TypeScript plus tsx appear under the development dependencies.
- Confirm the development script reports "tsx watch src/server.ts".
Your local TypeScript workspace is ready. Next, you will turn it into a lookup API with one partner grant plus a sample people dataset.
Build the Partner Lookup API
Your local TypeScript workspace is ready. It now needs a request path that turns partner context into a people lookup.
A partner can hold a valid key yet request records beyond its approved use. This HTTP API starts with a named grant that captures the partner user, stated purpose, declared population, and daily quota.
In this step, get ready to:
- Create an in-memory people dataset with permitted and out-of-population records.
- Define the partner grant for the approved research use.
- Run a permitted lookup that returns a sample person.
Define the people and partner grant
The people map gives the API local records to retrieve. The partner grant declares which caller is using those records under a specific purpose.
- In your project editor, select the top-level folder that contains package.json.
- Use the editor's folder control to create src inside that folder.
- Use the editor's file control to create server.ts inside src.
- Add the people dataset and partner grant to src/server.ts by pasting this code:
import express from "express";
// Describe the records and the access grant stored in memory.
type Person = { id: string; name: string; city: string };
type PartnerGrant = {
id: string;
partnerUser: string;
purpose: string;
allowedPersonIds: Set<string>;
dailyQuota: number;
};
// Include two permitted records and one record outside the grant population.
const people = new Map<string, Person>([
["person-1001", { id: "person-1001", name: "Freja Nielsen", city: "Copenhagen" }],
["person-1002", { id: "person-1002", name: "Mikkel Jensen", city: "Aarhus" }],
["person-9001", { id: "person-9001", name: "Sofie Larsen", city: "Odense" }],
]);
const partnerGrant: PartnerGrant = {
id: "grant-nordic-research",
partnerUser: "partner-analyst-1",
purpose: "approved demographic research",
allowedPersonIds: new Set(["person-1001", "person-1002"]),
dailyQuota: 3,
};
console.log(`Loaded ${people.size} people and grant ${partnerGrant.id}.`);
What does this code set up?
- The people map stores three sample records by person ID.
- The allowedPersonIds set declares person-1001 and person-1002 as the approved population.
- The person-9001 record sits outside that population for a later scope test.
- The dailyQuota value records the maximum daily volume that a later check can enforce.
- Save src/server.ts.
- Load the sample data with the development command from your existing terminal:
npm run dev
What does this command do?
The command runs the development script already configured in package.json. That script executes src/server.ts locally.
You'll see Loaded 3 people and grant grant-nordic-research. in the terminal.
Good progress. Your sample dataset and partner grant now load together with the exact context the endpoint needs.
- Press Ctrl+C if the development process remains active after printing the message.
Dataset not loading?
Confirm that server.ts is inside the src folder. Check that every person entry has matching brackets and quotes.
Compare the development script's entry file with src/server.ts. Help me diagnose why my TypeScript dataset does not load.
Add the lookup endpoint
An Express route matches the person ID in the URL. Its handler also reads the grant ID and partner user from the request query.
- In src/server.ts, find this line: console.log(`Loaded ${people.size} people and grant ${partnerGrant.id}.`);
- Add the lookup route directly below that line by pasting this code:
const app = express();
// Match the requested person with the supplied partner grant context.
app.get("/people/:personId", (req, res) => {
const personId = req.params.personId;
const grantId = String(req.query.grantId ?? "");
const partnerUser = String(req.query.partnerUser ?? "");
// Accept requests only from the configured grant and named partner user.
if (grantId !== partnerGrant.id || partnerUser !== partnerGrant.partnerUser) {
return res.status(403).json({ error: "Invalid partner grant context" });
}
const person = people.get(personId);
if (!person) {
return res.status(404).json({ error: "Person not found" });
}
return res.json({
grantId: partnerGrant.id,
purpose: partnerGrant.purpose,
partnerUser: partnerGrant.partnerUser,
person,
});
});
// Keep the local API available for lookup requests.
app.listen(3000, () => {
console.log("Partner lookup API running on http://localhost:3000.");
});
How does the lookup work?
- The /people/:personId route captures the requested person ID.
- The request query supplies grantId and partnerUser as the partner context.
- The first check rejects context that does not match the configured grant.
- A matching request receives the grant purpose plus the requested person record.
- Save src/server.ts.
- Start the completed lookup API from the existing terminal by running:
npm run dev
What starts now?
The development script loads the in-memory records. Express then listens for local requests on port 3000.
You'll see the dataset loading message followed by Partner lookup API running on http://localhost:3000. The active process keeps the endpoint available.
API not staying online?
Check that app.listen() appears after the closing bracket for app.get(). Confirm that no earlier process is already using port 3000.
Leave the terminal running once the startup message appears. Help me troubleshoot why my partner lookup API does not stay running.
✔️ Awesome, I've got everything!
Great. Your saved src/server.ts now contains the sample records, partner grant, lookup route, and local server.
ⓧ I'd like to double check the full code
import express from "express";
// Describe the records and the access grant stored in memory.
type Person = { id: string; name: string; city: string };
type PartnerGrant = {
id: string;
partnerUser: string;
purpose: string;
allowedPersonIds: Set<string>;
dailyQuota: number;
};
// Include two permitted records and one record outside the grant population.
const people = new Map<string, Person>([
["person-1001", { id: "person-1001", name: "Freja Nielsen", city: "Copenhagen" }],
["person-1002", { id: "person-1002", name: "Mikkel Jensen", city: "Aarhus" }],
["person-9001", { id: "person-9001", name: "Sofie Larsen", city: "Odense" }],
]);
const partnerGrant: PartnerGrant = {
id: "grant-nordic-research",
partnerUser: "partner-analyst-1",
purpose: "approved demographic research",
allowedPersonIds: new Set(["person-1001", "person-1002"]),
dailyQuota: 3,
};
console.log(`Loaded ${people.size} people and grant ${partnerGrant.id}.`);
const app = express();
// Match the requested person with the supplied partner grant context.
app.get("/people/:personId", (req, res) => {
const personId = req.params.personId;
const grantId = String(req.query.grantId ?? "");
const partnerUser = String(req.query.partnerUser ?? "");
// Accept requests only from the configured grant and named partner user.
if (grantId !== partnerGrant.id || partnerUser !== partnerGrant.partnerUser) {
return res.status(403).json({ error: "Invalid partner grant context" });
}
const person = people.get(personId);
if (!person) {
return res.status(404).json({ error: "Person not found" });
}
return res.json({
grantId: partnerGrant.id,
purpose: partnerGrant.purpose,
partnerUser: partnerGrant.partnerUser,
person,
});
});
// Keep the local API available for lookup requests.
app.listen(3000, () => {
console.log("Partner lookup API running on http://localhost:3000.");
});
Verify a permitted lookup
The requested person belongs to the grant's declared population. Before you send the lookup, do you expect this grant context to return a record or be refused?
- Press Cmd+Space on macOS or the Windows key on Windows to open system search.
- Type Safari on macOS or Microsoft Edge on Windows.
- Press Enter to open the browser.
- Click the browser's address bar.
- Enter http://localhost:3000/people/person-1001?grantId=grant-nordic-research&partnerUser=partner-analyst-1.
- Press Enter to send the lookup.
You'll see a JSON response with grantId set to grant-nordic-research.
Within the same response, person.id is person-1001.
What did this lookup prove?
- The endpoint received the person ID from the route.
- The supplied grant ID matched grant-nordic-research.
- The supplied partner user matched partner-analyst-1.
- The response connected the returned record to approved demographic research.
That first guarded lookup is working. A recognised partner context now receives the requested local record.
Lookup not returning the person?
Confirm that the terminal still shows the API startup message. Check that the browser URL contains person-1001.
Match the grantId and partnerUser query values to the grant in src/server.ts. Help me diagnose why my permitted lookup does not return person-1001.
Your partner lookup API now serves a valid record under a declared grant. Next, you'll make that declared population enforceable so an approved caller cannot query just anyone.
Enforce the Declared Population
Your first lookup proved that the trusted partner grant can reach the local API. Now you will put a record-level boundary around that access.
Authentication confirms who is calling. A declared population defines which person IDs that caller may query.
In this step, get ready to:
- Test how the current handler responds to an out-of-population lookup.
- Add a declared population check before record access.
- Prove permitted lookups still work while blocked lookups reveal no data.
Expose the current overreach
The sample dataset includes a person outside the grant's declared population. The current handler gives you a useful baseline because it still returns that person's record.
- Identify one sample person record whose key is absent from the grant's declared population.
- Record that person's ID here: your out-of-population person ID.
- Prepare a copy of the valid lookup request from the previous step.
- Replace its permitted personId with your out-of-population person ID.
Before you run it, predict whether the trusted grant can retrieve a person outside its declared population.
- Run the modified lookup request.
You will see the out-of-population person's record in the response. This is the intended shortfall that exposes the missing boundary.
Why did the trusted request return data?
The grant identifies an approved caller. Its declared population expresses the limit of that approval.
The current handler reads the dataset without enforcing that limit. A lawful credential can therefore expose a record outside the approved population.
Add the population guard
A population guard is an authorization check that applies the grant's existing scope to each request. Its position before the dataset lookup keeps blocked records out of the response.
- Switch back to the existing TypeScript source file that contains the GET /people/:personId handler.
- Locate the supplied grant validation inside GET /people/:personId.
- Add an early-return guard between the grant validation and the existing people dataset lookup. Its membership test uses the grant's existing declared population. Its rejection omits the person record.
- Save the TypeScript source file.
Why does the guard come first?
The handler must decide whether personId is allowed before reading its record. Early rejection prevents out-of-population data from leaving the API.
Before you test the guard, predict how the same out-of-population request responds now.
- Repeat the out-of-population lookup using your out-of-population person ID.
The response now rejects the request without returning the person record. That is the key boundary in place.
Still receiving the blocked record?
- Confirm the guard checks the current request's personId against the selected grant's declared population.
- Move the guard above the existing people dataset lookup.
- Restart the development server with the same command from the previous step if the running process did not reload your saved change.
If the record still appears, help me trace why my population guard is not rejecting an out-of-population person ID.
Verify both sides of the boundary
The guard must block overreach without breaking approved access. Testing both paths proves that the handler applies the grant's population precisely.
Before the final check, predict whether the permitted request still returns its record while the blocked request reveals no person data.
- Repeat the valid lookup request from the previous step with its permitted person ID.
- Repeat the blocked lookup with your out-of-population person ID.
The permitted request returns the requested sample record. The blocked request returns a rejection with no person data.
Your API now keeps the trusted grant inside its declared population. Next, you will enforce the grant's daily lookup quota.
Limit Daily Lookups
Your lookup API now blocks a partner from reaching people outside its declared population. Repeated requests for a permitted person still pass through unchecked.
A per-grant daily quota places a daily ceiling on lawful access. You will connect the grant's existing quota value to an in-memory usage counter.
In this step, get ready to:
- Demonstrate the unlimited lookup gap for a permitted person.
- Add a daily usage counter for each grant.
- Prove the API refuses a lookup beyond the quota.
Expose the unlimited lookup gap
Population scope controls which records a grant can reach. Volume control limits how many accepted records it can receive.
Before you repeat the request, predict whether the existing population check will stop repeated lookups for an allowed person.
- Return to the terminal from earlier.
- Reuse the earlier valid lookup request for a permitted person.
- Repeat that request more times than the grant's configured daily quota.
Every request still returns the permitted person record. You have exposed the gap that lets an authorised partner keep extracting allowed data.
Why Does the Population Check Still Pass?
Authentication proves which partner is calling. The population check confirms that each requested person belongs to the declared population.
A daily quota adds context from earlier accepted requests. The API can then stop a permitted caller after its approved volume is exhausted.
Enforce the grant's daily quota
The usage counter needs one entry per grant per date. This keeps grant-nordic-research separate from other grants across daily usage windows.
How Does the Quota Path Work?
- The handler creates the current usage key from the grant ID plus the current date.
- The counter holds the number of accepted lookups for that key.
- The handler rejects a request when the count has reached the grant's daily quota.
- An accepted lookup increments the count before the person record is returned.
- Return to the TypeScript source file from earlier that holds the partner grant configuration.
- Add an in-memory daily usage counter beside the existing grant data.
- Save the TypeScript source file.
You'll see the development server process the saved TypeScript change without a compilation failure.
Is the Counter Failing to Compile?
Confirm that the counter is initialized before the lookup handler uses it. Make sure its key can hold the grant ID plus the current date.
Check that the stored value represents an accepted lookup count. Help me debug my in-memory daily usage counter.
The quota placement is a little fiddly because it must run after the population check. This order prevents an out-of-population request from consuming approved capacity.
- Update the existing GET /people/:personId handler to apply the quota sequence above after the population check.
- Save the TypeScript source file.
You'll see the development server process the updated handler without a compilation failure.
Is the Handler Failing After the Update?
Confirm that the quota comparison happens before the person record is returned. Check that only an accepted lookup increments the stored count.
Make sure the handler reads the daily quota from the matching grant. Help me debug the quota check in my lookup handler.
Verify the daily limit
The next requests move the grant from available capacity to refusal. This test proves that the counter affects the live endpoint.
- Return to the terminal running the local API.
- Use a terminal loop to repeat the earlier valid lookup until the accepted request count matches the grant's daily quota.
Each request through the configured quota returns the permitted person record.
Before the next request, predict whether the same valid lookup will return another record or be refused.
- Send the same valid lookup request one more time.
You'll see the extra request refused without the person record. That is the daily boundary protecting this grant from unlimited extraction.
Is the Extra Lookup Still Succeeding?
Confirm that every accepted lookup writes its increment back to the daily counter. Check that the quota comparison uses the same grant-and-date key as the increment.
Verify that the rejection happens before the handler returns the person record. Help me find why repeated lookups never reach the daily quota.
✔️ Awesome, I've got everything!
Your quota flow is complete. The grant now refuses accepted lookups after its daily allowance is consumed.
ⓧ I'd like to double check the full code
Your finished quota flow has these checkpoints.
- The in-memory count is keyed by grant-nordic-research plus the current date.
- Accepted lookups increment the count for the matching grant-and-date key.
- The GET /people/:personId handler checks population scope before enforcing the daily quota.
- Requests after the quota are rejected without returning a person record.
Your partner grant now has a hard daily limit. Next, you will make every lookup traceable after it reaches the API.
Record Every Lookup
Your daily quota now stops a partner after its approved lookup volume is exhausted. The population check also blocks requests that reach beyond the declared scope.
An audit log preserves the context behind each decision. In this step, you will record allowed lookups plus rejected attempts in an append-only history.
In this step, get ready to:
- Define the information stored in each lookup audit row.
- Append an audit row for every allowed or rejected request.
- Prove that the audit log grows as the partner queries the API.
Define the audit record
An append-only history keeps earlier decisions intact. Each new request adds a row without replacing the rows already collected.
What belongs in each audit row?
- The timestamp records when the endpoint made its decision.
- The grant ID identifies the access agreement used for the request.
- The purpose preserves why the partner received access.
- The partner user identifies the person acting through the grant.
- The requested person ID identifies the record targeted by the lookup.
- The decision records either allow or reject.
- The records returned value measures how much data left the endpoint.
- Switch back to the editor tab that contains the GET /people/:personId handler.
- Locate the in-memory daily usage counter from the previous step.
- Create a typed in-memory collection beside that counter using the audit row structure above.
- Save the source file that contains the lookup handler.
- Switch back to the development server terminal from earlier to confirm the API reloads without type diagnostics.
That is the audit foundation in place: your API now has a typed home for every lookup decision.
Seeing a type diagnostic?
Check that every row field has one consistent type. Confirm that the in-memory collection accepts that complete row shape.
Compare the collection type with the seven fields listed above. Help me trace a type diagnostic in my lookup audit row.
Record every decision
Allowed responses prove that a grant worked within its limits. Rejected responses prove that a control stopped the partner before data left the endpoint.
How do decisions map to rows?
- An allowed lookup uses the allow decision with 1 record returned.
- An out-of-population lookup uses the reject decision with 0 records returned.
- An over-quota lookup uses the reject decision with 0 records returned.
- Every row for this partner records grant-nordic-research as the grant ID.
- Every row for this partner records approved demographic research as the purpose.
- Every row for this partner records partner-analyst-1 as the partner user.
- Update each response path in GET /people/:personId so it appends one complete audit row before sending its response.
- Save the source file that contains the lookup handler.
- Repeat the valid lookup request from the first step.
You will still receive the permitted person record. The audit write has preserved the endpoint's successful response.
- Switch back to the source file that contains the lookup handler.
- Add inspection output immediately after each audit row is appended.
- Save the source file that contains the lookup handler.
- Repeat the out-of-population request from the population-check step.
The lookup remains rejected without returning person data. Your development server terminal now shows a rejected audit row with 0 records returned.
Audit rows not appearing?
Confirm that the append happens before the response leaves each branch. A return placed first prevents the audit write from running.
Check that the inspection output runs immediately after the append. Help me find why my lookup audit rows are missing.
Verify the log fills
A useful audit history reconstructs both successful access and blocked overreach. The final sequence exercises every decision your endpoint can make for the configured partner.
Before you run the final sequence, think about the decision each request pattern should add.
- Repeat the valid lookup request using a permitted sample person ID.
- Repeat the rejected lookup request using the outside-population person ID from earlier.
- Run the quota loop from the previous step until the endpoint refuses a request.
Your terminal shows an allow row for each successful response. Each of those rows records 1 record returned.
The population refusal and quota refusal produce reject rows with 0 records returned. Every displayed row includes its timestamp plus the grant context behind the decision.
You now have a request history that shows who queried the API. It also shows why each lookup was allowed or refused.
Your audit trail now captures the evidence behind every lookup decision. Next, you will compare that history with normal volume so an abusive spike stands out.
Flag Abnormal Grant Activity
Your audit log now captures every lookup attempt made through the partner grant.
Request-path controls stop individual lookups. A baseline helps you spot a sudden surge across many requests.
In this step, get ready to:
- Add a normal lookup-volume baseline to the partner grant.
- Build a local monitor that aggregates audit rows by grant.
- Prove an abusive request burst triggers the grant flag.
Configure the grant baseline
The baseline represents the lookup volume expected from a grant during normal use. The monitor compares observed audit volume with this configured value.
- Locate the existing grant definition for grant-nordic-research in your TypeScript source.
- Add one lookup-volume baseline field to that grant.
- Choose a baseline below the request count produced by your repeated lookup loop from earlier.
- Save the grant definition.
Your grant now contains its expected lookup volume. Your editor should show no TypeScript errors for the new field.
Seeing a TypeScript error?
Check that the baseline is a numeric field inside the existing grant-nordic-research definition. Make sure the grant type accepts the same field.
Help me fix the baseline field in my partner grant.
Build the volume monitor
Anomaly detection turns individual audit rows into a pattern you can investigate. The audit data lives in memory.
The monitor needs direct access inside the same API process. This keeps the check connected to the rows recorded by the running server.
How should the monitor work?
- Group the audit rows by grant ID.
- Count every lookup attempt regardless of its decision.
- Read the expected volume from the matching grant baseline.
- Flag a grant only when its observed count exceeds that baseline.
- Create a local TypeScript monitoring script beside the source file that owns the in-memory audit data.
The new monitoring script should now appear beside your API source file.
- Implement an exported monitor function using the four rules above.
- Save the monitoring script.
Your editor should show an empty TypeScript error list. The monitor can now evaluate current audit volume against each grant baseline.
Monitor always reports zero rows?
Make sure the monitor receives the same in-memory audit collection used by the lookup handler. A separate collection created inside the monitoring script starts empty.
Help me connect my monitor to the existing audit rows.
Trigger and detect the abusive burst
The final connection runs the monitor after each audit row is recorded. This gives the monitor the latest request before it evaluates the grant.
- Locate the statement that appends each lookup attempt to the audit log.
- Invoke the monitor immediately after that append with the current audit rows and configured grants.
- Save the API source file.
- Restart the API with the development server command from earlier.
The terminal should show the same ready or listening confirmation you saw before. This confirms the API compiled with the monitor connected.
Why run the monitor after every append?
- Running the monitor after the append includes the current request in its count.
- Counting rejected attempts exposes repeated probing after the daily quota starts refusing lookups.
Before you run the burst, do you think the quota alone prevents the activity spike from appearing in the audit volume?
- Return to the repeated lookup loop from earlier.
- Run the same loop with enough requests to exceed the configured baseline.
You should see the monitor report grant-nordic-research as flagged after its observed volume exceeds the baseline.
Some requests may be rejected by the quota. Those attempts still contribute to the abnormal volume signal.
Grant not being flagged?
Confirm that the baseline is smaller than the total number of requests in the loop. Check that the monitor counts every audit row for grant-nordic-research.
Help me diagnose why the abusive lookup run is not triggering my grant-volume flag.
That closes the monitoring gap. The flagged grant proves your audit data can expose abuse across a burst of authenticated requests.
Secret mission
Investigate the Flagged Partner Grant
Turn the volume alert for `grant-nordic-research` into a focused incident review. Use the audit trail to explain what happened and recommend a containment action without changing the running API.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep the local API running, pause it for later, or remove the project from your computer. Everything runs on your laptop, so leaving the files in place creates no ongoing service cost.
Resources you used:
- A local TypeScript project directory containing the HTTP lookup API plus the grant volume monitor.
- A local Node.js development server process accepting lookup requests.
- An in-memory runtime state containing the partner grant, people dataset, daily usage counter, audit rows, and monitoring data.
Keep everything running
No action is needed while you are actively testing the API. The local server continues using a small amount of memory without creating a service charge.
- Leave the local API process running while you continue testing partner lookups.
- Keep the project directory as a reusable pattern for APIs you expose to partners.
- Preserve the current server process if you want to inspect the existing in-memory audit rows.
Pause - I'll come back to this later
Stopping the development server frees its local memory while preserving your source files. You can start the server again with the development command you used earlier.
- Switch back to the terminal that is running the API.
- Press Ctrl+C to stop the development server.
- Keep the local project directory on your computer.
The API no longer accepts lookup requests. Its in-memory counters and audit rows clear when the process stops.
Delete - I don't want to use this again
Deleting the project directory is permanent. This cleanup affects only the local project because the API created no external resources.
- Copy any handler pattern you want to reuse to a different location.
- Switch back to the terminal that is running the API.
- Press Ctrl+C to stop the development server.
- Use your computer's file browser to open the location containing the project directory that your terminal was using.
- Select the local project directory used for this project.
- Delete the selected project directory with your file browser's delete action.
- Clear your computer's deleted files to make the removal permanent.
The local project files are now removed. The in-memory data disappeared when you stopped the server.
Nice Work!
Nice Work!
Mission complete! You built a local TypeScript API that controls which people a trusted partner may query. It also exposes abusive access through grant volume monitoring.
You've learned how to:
- Build a partner lookup API that returns permitted person records. Reject requests outside the grant's declared population.
- Enforce a daily lookup quota for each grant. Refuse requests after the limit is reached.
- Record every lookup attempt in an audit trail. Flag abusive volume with a grant monitor.
- Secret Mission: Take on an optional challenge to push your API access control skills further.
Ready to quiz yourself?