Agent Skills in Claude Code and Visual Studio Code: Setup, Benefits, Risks, and a Step-by-Step Example
For the Personal Reading List Tracker, an API could help users search for a book title, find an author, or retrieve a cover image. Instead of entering every detail manually, the user could search for a book and select a result.
That convenience introduces new questions.
Does the API require a key? Where should that key be stored? Can it be used directly from browser JavaScript? What happens when the API is unavailable? Should the application still work without it?
These questions matter because AI coding assistants often generate API integrations that appear functional while exposing credentials or creating fragile dependencies.
This article explains how to use APIs and environment variables safely in a beginner-friendly project.
By the end of this guide, you should be able to:
An application programming interface, or API, allows one software system to request data or actions from another system.
A book information API might accept a request such as:
Search for books with the title "Kindred"It may return structured data:
{
"items": [
{
"title": "Kindred",
"authors": ["Octavia E. Butler"],
"thumbnail": "https://example.com/cover.jpg"
}
]
}Your application can use that response to display search results.
A typical flow looks like:
User enters a search term
→ App sends an HTTP request
→ API returns JSON
→ App validates the response
→ App displays resultsThe API does not become part of your application’s codebase. It remains an external service with its own rules, availability, limits, and data format.
Before adding environment variables, determine whether the API requires authentication.
An API may be:
These categories should not be treated as interchangeable.
A public API request might look like:
const response = await fetch(
`https://api.example.com/books?q=${encodeURIComponent(searchTerm)}`
);No secret is included.
A protected request might require:
const response = await fetch("https://api.example.com/books", {
headers: {
Authorization: `Bearer ${API_KEY}`
}
});If this code runs in the browser, users can inspect the request and recover the key.
The presence of an environment variable does not change that fact.
Code delivered to a browser is visible to the person using it.
Users can inspect:
Suppose an AI assistant generates:
const API_KEY = "my-secret-api-key";The problem is obvious because the key appears directly in the file.
Now consider:
const API_KEY = import.meta.env.VITE_BOOK_API_KEY;This may look safer because the value comes from an environment variable.
However, many frontend build tools replace that variable with its value during the build process. The resulting value can still become part of the JavaScript sent to users.
The exact naming conventions and behavior vary by tool, so current official documentation should be checked before publication or deployment. The general security rule remains stable:
A secret used directly by browser code should be assumed visible to users.
An environment variable stores configuration outside the main source code.
Examples include:
BOOK_API_BASE_URL
BOOK_API_KEY
APP_ENVIRONMENT
LOG_LEVELEnvironment variables are useful because they allow different settings for:
For example:
Development API:
https://dev-api.example.com
Production API:
https://api.example.comThe application can select the correct value without hard-coding both URLs into the same file.
Environment variables also reduce the chance of accidentally writing a secret directly into source code.
They do not guarantee that a secret stays secret.
The final level of protection depends on where the value is used.
A server-side environment variable can remain private when:
A client-side environment variable is usually configuration provided to browser code.
Examples of acceptable public client configuration may include:
Examples of values that should not be exposed include:
A variable name can include the word SECRET and still be exposed if the build system sends it to the browser.
Security depends on execution location, not the variable’s name.
When an API requires a private credential, browser code should not call it directly with that credential.
A safer structure is:
Browser
→ Your server or serverless function
→ External APIThe browser sends a limited request to your own backend endpoint.
The backend:
For example:
Browser request:
GET /api/books?q=kindred
Server request:
GET https://external-api.example/search?q=kindred
Authorization: Bearer [private server-side key]The browser receives book results, not the private key.
A proxy still needs protection.
It should consider:
Do not ask an AI assistant to “hide the key behind an endpoint” and assume the work is complete.
The endpoint itself may become publicly callable and consume your API quota.
The Personal Reading List Tracker does not need an external API to perform its main function.
Users can already:
This means API search should be optional.
A useful architecture is:
Core application:
Manual book entry works locally
Optional enhancement:
Search external book data
Fallback:
User can continue entering books manuallyThis design prevents an external outage from making the entire application unusable.
The core project remains useful even when:
Avoid asking the AI to add several API-based features at once.
A weak task might be:
Connect the app to a book API, add covers, recommendations, ratings, author pages, categories, and automatic imports.A focused task is:
Add an optional book-search field that returns up to five matching titles and authors. Selecting one result fills the existing title and author inputs. Manual entry must continue to work.This task has a visible outcome and clear boundaries.
It does not replace the existing application workflow.
Before generating code, record the expected behavior.
For example:
Feature:
Optional book search
User workflow:
1. Enter at least two characters.
2. Submit the search.
3. View up to five results.
4. Select one result.
5. Populate the existing title and author fields.
6. Review the values.
7. Add the book through the existing form.
Fallback:
Manual entry remains available at all times.Add failure behavior:
- Empty search is rejected.
- No results display a useful message.
- Network failure does not clear existing form data.
- Invalid API data is ignored safely.
- The search button cannot trigger unlimited duplicate requests.
- The application does not automatically save a search result.These requirements prevent the API feature from taking control of the core data workflow.
Before using an external service, verify:
These details can change over time.
Current provider documentation should be checked before implementation and publication.
Do not rely only on an AI assistant’s memory of an API.
The AI may suggest:
Treat official documentation and actual test responses as evidence.
An API integration may affect:
Start from a clean checkpoint:
git statusCreate a branch:
git switch -c feature/book-search-apiConfirm:
git branch --show-currentThe output should show:
feature/book-search-apiThis separates the experiment from the verified main application.
A project should not contain:
const API_KEY = "real-secret-value";It should also not contain a committed .env file with a real credential:
BOOK_API_KEY=real-secret-valueAdd environment files containing secrets to .gitignore.
A common pattern is:
.env
.env.local
.env.*.localThe correct files depend on the project’s tools, so review the generated ignore rules rather than copying them blindly.
Then confirm:
git statusThe secret file should not appear as a file ready to commit.
You may create:
.env.examplewith placeholders:
BOOK_API_KEY=replace-with-your-own-key
BOOK_API_BASE_URL=https://api.example.comThe example documents required configuration without exposing a real credential.
Do not include a realistic-looking value that could be mistaken for a working secret.
Adding a file to .gitignore does not remove it from Git when it is already tracked.
Check:
git status
git ls-filesIf a secret file was committed, removing it from the latest version may not erase it from earlier history.
The credential should be treated as exposed.
The correct response usually includes:
Rotating the credential is more important than trying to pretend the original value was never exposed.
Avoid pasting real credentials into:
Use placeholders:
BOOK_API_KEY=[REDACTED]Or:
Authorization: Bearer [REDACTED]An AI assistant rarely needs the real value to help with request structure or error handling.
Suppose the user searches for:
The Left Hand of DarknessDo not concatenate raw input directly:
const url = `https://api.example.com/search?q=${searchTerm}`;Use URL encoding:
const url = `https://api.example.com/search?q=${encodeURIComponent(searchTerm)}`;A stronger approach uses the URL API:
const url = new URL("https://api.example.com/search");
url.searchParams.set("q", searchTerm);
url.searchParams.set("limit", "5");This produces a properly encoded query.
Encoding does not replace server-side validation. It ensures the value is represented correctly in the URL.
Do not send a request for every invalid input.
For example:
function normalizeSearchTerm(value) {
if (typeof value !== "string") {
return "";
}
return value.trim();
}Then:
const searchTerm = normalizeSearchTerm(searchInput.value);
if (searchTerm.length < 2) {
showSearchMessage("Enter at least two characters.");
return;
}This reduces unnecessary requests and provides clearer feedback.
Depending on the API, you may also enforce a reasonable maximum length.
Avoid presenting arbitrary limits as universal. Choose them based on the application and provider documentation.
An API response may contain much more information than the application needs.
The reading list may need only:
title
author
thumbnail URL
external identifierIt may not need:
Map the response into your own smaller structure:
function mapSearchResult(item) {
return {
externalId: item.id ?? "",
title: item.title ?? "",
author: item.authors?.[0] ?? "",
thumbnail: item.thumbnail ?? ""
};
}Do not store entire external responses in local storage merely because they are available.
Keeping less data makes the application easier to understand and reduces unnecessary exposure.
External data should not be trusted automatically.
Check:
For example:
if (!response.ok) {
throw new Error(`Book search failed with status ${response.status}`);
}
const data = await response.json();
if (!Array.isArray(data.items)) {
return [];
}Then normalize each item.
An API may change, return an error object, or provide incomplete records.
The application should handle those cases without crashing.
innerHTMLExternal API data is untrusted input.
Do not render it by directly building HTML strings:
resultsContainer.innerHTML += `
<button>${book.title}</button>
`;Use DOM elements and textContent:
const resultButton = document.createElement("button");
resultButton.type = "button";
resultButton.textContent = book.title;For images, validate and assign URLs carefully.
Also provide useful alternative text:
coverImage.alt = book.title
? `Cover of ${book.title}`
: "Book cover";Do not assume that data from a well-known API is always safe to insert as markup.
A search request takes time.
The interface should explain that work is in progress.
For example:
Searching for books…During the request, you may:
aria-busy state;A simple flow is:
setSearchLoading(true);
try {
const results = await searchBooks(searchTerm);
renderSearchResults(results);
} catch (error) {
showSearchError("Book search is temporarily unavailable.");
} finally {
setSearchLoading(false);
}The finally block helps ensure the loading state is cleared whether the request succeeds or fails.
An external API can fail because of:
The application should display a useful message:
Book search is unavailable right now. You can still enter the book manually.Avoid messages that expose internal details:
Request failed using API key abc123 at internal endpoint...Technical details may be logged carefully for development, but user-facing messages should explain what the user can do next.
A request may remain pending longer than expected.
Modern browser APIs may support request cancellation through AbortController.
A simplified example:
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, 8000);
try {
const response = await fetch(url, {
signal: controller.signal
});
return await response.json();
} finally {
clearTimeout(timeoutId);
}The timeout value should reflect the application’s needs rather than being treated as a universal rule.
Also consider cancelling an older search when the user starts a new one.
This prevents late responses from replacing newer results.
An AI assistant may generate:
searchInput.addEventListener("input", searchBooks);This could send a request for every typed character.
Potential problems include:
Safer beginner options include:
For the first integration, a Search button is simpler to review.
External services may limit:
Do not hard-code assumptions based on an AI response.
Check current provider documentation.
Design the feature so that reaching a limit does not destroy the core workflow.
For the reading list tracker:
API available:
Search and prefill book data
API unavailable:
Enter title and author manuallyThis fallback keeps the application useful.
Before sending data to an API, ask:
For book search, the app may send only:
Search term: "Kindred"It does not need to send:
Collect and transmit the minimum data required.
Use a focused review checklist.
response.ok checked?textContent?.env appear in Git status?Do not ask the AI to code the integration immediately.
Start with inspection:
Read PROJECT_BRIEF.md and inspect the Personal Reading List Tracker without changing files.
I want to add an optional book-search feature.
Requirements:
- Manual entry must continue to work.
- Search results may fill the existing title and author inputs.
- Selecting a result must not save the book automatically.
- Display no more than five results.
- Handle loading, no-results, and network-error states.
- Do not store the full API response.
- Do not add a dependency unless there is a clear need.
Security:
- Determine whether the proposed API requires a private key.
- If a private key is required, do not place it in browser code.
- Explain whether a server-side proxy would be necessary.
- Do not request, display, or create a real credential.
Process:
1. Identify the files that would need changes.
2. Describe the request and response data.
3. List security and failure risks.
4. Propose the smallest implementation plan.
5. Do not edit files yet.This prompt asks the AI to reason about the architecture before generating code.
When the selected API can be called publicly from the browser and current documentation confirms that use:
Implement optional book search using the approved public endpoint.
Files allowed to change:
- index.html
- styles.css
- app.js
Expected behavior:
- A user submits a search term of at least two characters.
- Show a loading state.
- Display up to five normalized results.
- Each result shows title and author.
- Selecting a result fills the existing title and author fields.
- Manual entry remains available.
- No result is automatically added to the reading list.
- No-results and network-error states are visible.
Constraints:
- Do not add dependencies.
- Do not change the existing book data model.
- Do not modify localStorage behavior.
- Do not use innerHTML for external data.
- Do not add a private API key.
- Do not log the complete API response.
Before editing:
- Show the exact endpoint and fields you plan to use.
- Confirm they match the current official API documentation.
- List the files and functions you will modify.After implementation, independently verify the documentation and actual network behavior.
When a private key is required:
Do not implement the browser request yet.
Design a minimal server-side proxy for book search.
Requirements:
- The private API key must remain server-side.
- The browser sends only the normalized search term.
- The server validates input.
- The server requests only the required external fields.
- The response returns only title, author, thumbnail, and external ID.
- Include a plan for rate limiting and error handling.
- Do not include a real credential.
- Do not commit an environment file.
Explain:
- Which code runs in the browser.
- Which code runs on the server.
- Which environment variable is required.
- How the deployment platform should receive the secret.
- How to test without exposing the key.This is a larger architectural change than a static-site feature.
Consider whether the benefit justifies adding a backend or serverless function to a beginner project.
For a public API feature, test:
- Normal search term
- Search with spaces
- Empty search
- One-character search
- No matching results
- Network failure
- Repeated search
- Selecting a result
- Returning to manual entryFor a protected server-side integration, also test:
- Missing server-side environment variable
- Invalid credential
- Provider rate-limit response
- Invalid query
- Unexpected response structure
- Proxy error without credential exposureDo not print a real key to confirm that it loaded.
A safe diagnostic might report:
BOOK_API_KEY is configured.It should not report the value.
Run:
git status
git diffLook for:
.env;Search the changed files for suspicious patterns:
git diff --stagedThen review manually for words such as:
api_key
secret
token
authorization
password
bearerA text search can help, but it does not prove that no secret exists.
Update the README when the integration requires configuration.
Document:
Do not place the real value in the documentation.
For example:
Required server-side variable:
BOOK_API_KEY
Do not:
- place the value in app.js;
- prefix it for client-side exposure;
- commit it to Git;
- paste it into screenshots or issues.Anyone using the application can inspect it.
Frontend build variables may be included in public bundles.
.envIgnore rules must be in place before the file is tracked.
Development and production may require separate credentials and permissions.
Debug output can leak secrets into terminals, build logs, or monitoring systems.
API text should not be inserted through unsafe HTML rendering.
The reading list should still support manual entry.
A network request can fail even when the code is correct.
Transmit only what the feature requires.
Verify endpoints, authentication, limits, and policies against current official documentation.
Use this process before integrating an API.
Does the API require authentication?
No
→ Confirm browser use is allowed
→ Validate input and response
→ Handle failures
→ Keep the feature optional
Yes
→ Is the credential intended to be public?
Yes
→ Confirm provider restrictions and documentation
→ Treat the value as publicly visible
→ Limit permissions and usage
No
→ Do not place it in browser code
→ Use a server-side proxy
→ Store the key in server-side environment configuration
→ Add validation, rate limiting, and error handlingWhen the architecture becomes too complex for the project’s purpose, do not add the API yet.
Manual entry is a valid product decision.
API integrations often appear small because the successful request is only a few lines of code.
The real work includes:
AI coding assistants are good at producing a successful request example.
They are less reliable at automatically understanding your security boundary, deployment environment, or acceptable failure behavior.
A useful review question is:
What happens when the external service is slow, unavailable, incorrect, or compromised?
For the Personal Reading List Tracker, the safest answer is that manual entry continues to work.
.gitignore..env is not tracked.APIs can improve a vibe-coded application, but they also introduce external dependencies, failure states, privacy questions, and credential risks.
The most important rule is simple:
Do not place a private secret in code that runs in the browser.
Environment variables are useful for configuration, but they are not automatically private. Server-side variables can protect credentials when they remain on the server. Client-side variables should be treated as publicly visible.
For the Personal Reading List Tracker, external book search should remain optional. Manual entry should continue to work when the API is unavailable, returns no results, or is not configured.
The next article will focus on testing and deployment. We will create a final verification plan, review the production build or static files, check the deployed application, and confirm that no secret or development-only artifact was published.
Only when the provider explicitly treats that key as public and provides appropriate restrictions.
A private credential should not be placed in browser code, even when it comes from an environment variable.
.env file enough to protect a secret?No.
The file must remain untracked, the value must be used only in a private execution environment, and deployment or build systems must not expose it to the browser or logs.
No.
The core application works through manual entry and local storage. An API can be added as an optional enhancement when its value justifies the additional complexity.
Comments
Post a Comment