Claude Code Quickstart
Mint an Agent ISSO key, verify it, walk one control from not-met to met with evidence and a POA&M using curl, then hand the same API to Claude Code.
This walkthrough takes about twenty minutes. By the end you will have an
Agent ISSO key, a working curl session against your own package, and a
Claude Code project that can do the same work from a prompt.
You need an Owner account to enable Agent ISSO (an Admin can do
everything after that), a Level 2 package with at least one control still
open, and curl and jq on your machine.
1. Enable Agent ISSO
Sign in as an Owner and open Settings → Agent ISSO. Flip the switch in the Enable Agent ISSO row. That is the whole step — nothing changes for anyone until a key exists.
2. Mint a key
Click New key:
- Name:
claude-code (your name) - Expires in: 90 days
- Scopes: tick Read and Write for Controls, Evidence, and POA&Ms, and Read for Packages. Leave the rest unticked.
That grants exactly:
packages:read controls:read controls:write evidence:read evidence:write poams:read poams:writeClick Create and copy the token from the reveal dialog — it is shown once. Put it in your shell, never in a file you might commit:
export BEDROCK_CMMC_TOKEN='bcmmc_…paste the whole token…'
export BEDROCK_CMMC_API='https://api.bedrock-cmmc.com/api/v1'3. Verify the key
curl -s "$BEDROCK_CMMC_API/me" -H "Authorization: Bearer $BEDROCK_CMMC_TOKEN" | jq{
"data": {
"org": { "id": "org_01HX…", "name": "Acme Defense LLC", "orgType": "OSC" },
"key": {
"id": "3f9a1c2e8b7d4650",
"name": "claude-code (jane)",
"scopes": ["packages:read", "controls:read", "controls:write", "evidence:read", "evidence:write", "poams:read", "poams:write"],
"expiresAt": "2026-12-01T14:02:11Z",
"lastUsedAt": null
}
},
"error": null
}Every response is wrapped like this — the payload is in data, and a
failure puts null there and a message and code in error. The jq
filters below all start with .data.
If you see AGENT_ISSO_DISABLED, step 1 was skipped. UNAUTHORIZED usually
means the token was truncated on paste — it is about 66 characters.
4. A full cycle with curl
Every command below uses a small helper so the auth header is not repeated:
bc() { curl -s "$BEDROCK_CMMC_API$1" -H "Authorization: Bearer $BEDROCK_CMMC_TOKEN" "${@:2}"; }Find your package
bc /packages | jq -r '.data[] | "\(.id)\t\(.cmmcLevel)\t\(.name)"'
export PKG=pkg_01J9X3M4V6Q8 # the id of the package you want to work onList the open Access Control requirements
bc "/packages/$PKG/controls?family=AC&status=NON_COMPLIANT" \
| jq -r '.data[] | "\(.requirementId)\t\(.points)pt\t\(.objectivesMet)/\(.objectivesTotal)\t\(.title)"'03.01.03 1pt 2/4 Information Flow Enforcement
03.01.10 1pt 1/3 Session Lock
03.01.12 5pt 1/3 Remote Access ControlTwo of those are 1-point, POA&M-eligible requirements; 03.01.12 is a
5-point certification blocker that must actually be fixed. We will work
03.01.03.
Read the control and pick an objective
Control paths accept the requirement code directly, so you never have to look up an internal id:
bc "/packages/$PKG/controls/03.01.03" \
| jq -r '.data.control.requirement.objectives[] | "\(.id)\t\(.objectiveId)\t\(.statuses[0].status // "NOT_ASSESSED")\t\(.description)"'obj_01HZ7A3D1M 03.01.03.a MET Information flow control policies are defined.
obj_01HZ7A3D1N 03.01.03.b MET Methods and enforcement mechanisms for controlling the flow of CUI are defined.
obj_01HZ7A3D1P 03.01.03.c NOT_MET Designated sources and destinations for CUI within the system and between interconnected systems are identified.
obj_01HZ7A3D1Q 03.01.03.d NOT_MET Authorizations for controlling the flow of CUI are defined and enforced.Mark objective c met, with an assessment note
You have just confirmed the CUI source/destination inventory exists. Record it:
bc "/packages/$PKG/objectives/obj_01HZ7A3D1P" -X PATCH -H "Content-Type: application/json" -d '{
"status": "MET",
"assessmentNotes": "Examined CUI data-flow register v2 (2026-08-30): lists the GCC High tenant, the lab file server, and the two prime-contractor SFTP endpoints as the only CUI sources and destinations. Interviewed the enclave admin, who confirmed no other paths exist.",
"policyReference": "AC-4 Information Flow Policy v3"
}' | jq '.data | {status, version}'The control stays NON_COMPLIANT because objective d is still open — but
the objective flip and your note are now in the audit feed under actor
agent · claude-code (jane).
Attach the evidence
Create the record, upload the file to the URL it returns, confirm the upload, then link it to the objective you just assessed:
FILE=cui-data-flow-register-v2.pdf
CREATE=$(bc "/packages/$PKG/evidence" -X POST -H "Content-Type: application/json" -d "{
\"fileName\": \"$FILE\",
\"mimeType\": \"application/pdf\",
\"fileSize\": $(stat -c %s "$FILE"),
\"description\": \"CUI data-flow register v2, approved 2026-08-30\",
\"evidenceType\": \"PLAN\"
}")
EV_ID=$(jq -r '.data.evidence.id' <<<"$CREATE")
# Upload the bytes with exactly the method and headers the API returned
curl -s -X "$(jq -r '.data.uploadMethod' <<<"$CREATE")" "$(jq -r '.data.uploadUrl' <<<"$CREATE")" \
$(jq -r '.data.uploadHeaders | to_entries[] | "-H \"\(.key): \(.value)\""' <<<"$CREATE" | xargs) \
--data-binary @"$FILE"
# Tell Bedrock the bytes landed (409 UPLOAD_NOT_FOUND if they did not)
bc "/packages/$PKG/evidence/$EV_ID/confirm" -X POST | jq
# Link it to objective c
bc "/packages/$PKG/evidence/$EV_ID/links" -X POST -H "Content-Type: application/json" \
-d '{ "objectiveId": "obj_01HZ7A3D1P" }' | jqOpen the control in the app: the file appears under objective c with a
paper-clip icon, exactly as if you had uploaded it from the Evidence page.
Open a POA&M for what is still not met
Objective d needs an engineering change that will take a few weeks.
03.01.03 is a 1-point requirement and POA&M-eligible, so file it:
bc "/packages/$PKG/poams" -X POST -H "Content-Type: application/json" -d '{
"title": "Enforce CUI flow authorizations at the enclave egress",
"description": "03.01.03 objective d not met: flow authorizations are documented but not enforced for the lab file server to SFTP path.",
"type": "ASSESSMENT",
"riskLevel": "MODERATE",
"remediationPlan": "Add Purview DLP egress rule for the lab file server; restrict SFTP to the two authorized prime endpoints; capture rule export as evidence.",
"scheduledCompletionDate": "2026-10-15",
"requirementIds": ["03.01.03"]
}' | jq '.data | {id, status, deadline}'Had you tried the same call for 03.01.12 (5 points), the API would have
refused with 422 POAM_NOT_ALLOWED and created nothing — the methodology
does not let a POA&M stand in for a 5-point requirement, and neither does
Bedrock.
Check the score moved
bc "/packages/$PKG/sprs" | jq '.data | {score: .sprs.score, blockers: (.blockers | length), poamEligible: (.poamEligible | length)}'That requires sprs:read, which this key does not have — you will get
403 with error.code FORBIDDEN_SCOPE naming the scope. Add it in a rotated key if you want
your agent to report the score; the point is that a key can only do what you
said it could.
5. Hand it to Claude Code
Everything above is what Claude Code will do for you once it knows the API exists, where the token lives, and what the rules are. Three pieces make that reliable.
Tell Claude Code about the API
Add this to the CLAUDE.md in the project you run Claude Code from (or to
~/.claude/CLAUDE.md for every project). It is the contract Claude Code
reads at the start of each session:
# Bedrock CMMC — Agent ISSO API
- Base URL: https://api.bedrock-cmmc.com/api/v1
- Auth: `Authorization: Bearer $BEDROCK_CMMC_TOKEN` (the token is in the
environment; never print it, never write it to a file, never pass it on
a command line where it would land in shell history).
- Reference: https://docs.foxxcyber.com/docs/bedrock-cmmc/automation/api-reference/
Machine-readable: GET /api/v1/openapi.json (no auth).
- Every response is `{"data": ..., "error": null}`; on failure `data` is
null and `error.code` says why. Read results from `.data`.
- Control paths take the requirement code directly (`/controls/03.01.03`).
- Package id for this project: pkg_01J9X3M4V6Q8
## Rules
- Never delete anything. The API has no deletes and you must not look for
a workaround.
- Always show me the exact objective, the new status, and the assessment
note, and wait for my confirmation before any PATCH that changes a status.
- Assessment notes must say what was examined, interviewed, or tested and
when — no "confirmed compliant" without the artifact.
- Before creating a POA&M check `poamAllowed` for the requirement via
GET /packages/{id}/sprs; if it is false, tell me it is a blocker instead.
- Read before you write: fetch the control detail and quote the current
status back to me before proposing a change.
- On 403 FORBIDDEN_SCOPE stop and tell me which scope is missing; do not
retry.Set the token in the shell you launch Claude Code from:
export BEDROCK_CMMC_TOKEN='bcmmc_…'
claudeThen try a first prompt:
Using the Bedrock CMMC API, list the NON_COMPLIANT controls in family AC for our package, sorted by points descending. For each, show the objectives that are NOT_MET. Don't change anything yet.
A bulk action
Once Claude Code can read your package, it can do the repetitive part of an ISSO's week. Say you have a folder of PDFs named after the objectives they satisfy:
evidence/
03.01.03.c-cui-data-flow-register.pdf
03.01.10.b-session-lock-gpo.pdf
03.01.10.c-session-lock-screenshots.pdf
03.05.03.a-mfa-policy.pdfUpload every PDF in ./evidence to our Bedrock CMMC package as evidence (evidenceType PLAN for policies and registers, SCREENSHOT for screenshots) and link each one to the objective named at the start of its filename —
03.01.10.bmeans requirement 03.01.10, objective b. Resolve objective ids from the control detail endpoint; if a filename doesn't match exactly one objective, skip it and tell me. Show me the plan before uploading anything, and don't change any objective statuses.
Claude Code will read openapi.json, walk the controls to resolve
03.01.10.b to an objective id, create each evidence record, PUT the bytes
to the presigned URL, confirm, and post the link — then report a table of
what landed where. Because the key has no delete and the instructions forbid status
changes without confirmation, a misnamed file can at worst produce an
unlinked upload you can tidy from the Evidence page.
MCP: give Claude Code tools instead of curl
curl works, but Claude Code is at its best when the API shows up as typed
tools it can call directly. The Model Context Protocol (MCP) is how
Claude Code loads tools, and because the agent API publishes an OpenAPI
document, you do not have to write an MCP server — any OpenAPI-to-MCP
bridge can generate one from GET /api/v1/openapi.json.
The pattern is the same whichever bridge you use: point it at the OpenAPI
URL, give it the base URL and a way to inject
Authorization: Bearer … from an environment variable, run it as a
stdio MCP server, and register it in the project's .mcp.json. Each
endpoint becomes a tool (listPackages, patchObjective, …) with the
request schema attached, so Claude Code fills in fields correctly instead of
hand-building JSON.
One well-maintained option is FastMCP
(Python, Apache-2.0), whose FastMCP.from_openapi() does exactly this. A
complete bridge is a dozen lines:
# bedrock_mcp.py
import os
import httpx
from fastmcp import FastMCP
BASE = "https://api.bedrock-cmmc.com/api/v1"
client = httpx.AsyncClient(
base_url=BASE,
headers={"Authorization": f"Bearer {os.environ['BEDROCK_CMMC_TOKEN']}"},
)
spec = httpx.get(f"{BASE}/openapi.json", timeout=30).json()
mcp = FastMCP.from_openapi(openapi_spec=spec, client=client, name="bedrock-cmmc")
if __name__ == "__main__":
mcp.run() # stdioRegister it in .mcp.json at the root of your project. Claude Code expands
${VAR} from the environment it was launched in, so the token stays out of
the file:
{
"mcpServers": {
"bedrock-cmmc": {
"command": "uv",
"args": ["run", "--with", "fastmcp", "python", "bedrock_mcp.py"],
"env": { "BEDROCK_CMMC_TOKEN": "${BEDROCK_CMMC_TOKEN}" }
}
}
}Start Claude Code, run /mcp to confirm bedrock-cmmc connected and to see
its tool list, and the prompts above work unchanged — Claude Code will
simply call getPackageControls instead of composing a curl. Keep the
CLAUDE.md rules in place: the bridge gives Claude Code the ability to call
the API, the rules tell it when it may.
Other tools work the same way
OpenAI Codex, a GitHub Actions job, or a plain Python script need nothing else: the OpenAPI document, the bearer header, and a key with the right scopes. Anything that speaks HTTPS can be your ISSO's hands.
Keep the key safe
Treat the token like a password to your compliance record
- Environment variable, not a file. Export
BEDROCK_CMMC_TOKENin the shell (or a secret manager that injects it). Never put the literal token inCLAUDE.md,.mcp.json, a script, or a commit — add.envto.gitignoreif you must use one locally. - Never commit it. If a token lands in git, treat it as leaked even if you rewrite history.
- Rotate on any leak, and on every departure. Settings → Agent ISSO → Rotate issues a replacement and lets the old key live until you Revoke it. The Last used column tells you when the swap is done.
- Least scope, shortest expiry. A reporting job gets
*:readand 30 days. Write scopes and 365-day keys are for services you rotate on a schedule. - Kill switch. An Owner turning Enable Agent ISSO off rejects every key instantly, without deleting anything — use it first, then investigate.
Cheat sheet
export BEDROCK_CMMC_TOKEN='bcmmc_…'
export BEDROCK_CMMC_API='https://api.bedrock-cmmc.com/api/v1'
bc() { curl -s "$BEDROCK_CMMC_API$1" -H "Authorization: Bearer $BEDROCK_CMMC_TOKEN" "${@:2}"; }
# PKG = package id; CTRL = requirement code such as 03.01.03; OBJ/EV/POAM = ids from the API
# Every response is {"data": ..., "error": null} — pipe through `jq .data`
bc /me # who am I, what can I do
bc /packages # packages I can reach
bc "/packages/$PKG/controls?status=NON_COMPLIANT" # what is open
bc "/packages/$PKG/controls?family=AC" # one family
bc "/packages/$PKG/controls/$CTRL" # objectives + evidence
bc "/packages/$PKG/objectives/$OBJ" -X PATCH -H "Content-Type: application/json" \
-d '{"status":"MET","assessmentNotes":"…"}' # assess an objective
bc "/packages/$PKG/controls/$CTRL/notes" -X PATCH -H "Content-Type: application/json" \
-d '{"implementationNotes":"…"}' # control notes
bc "/packages/$PKG/evidence" # evidence list
bc "/packages/$PKG/evidence" -X POST -H "Content-Type: application/json" \
-d '{"fileName":"x.pdf","mimeType":"application/pdf","fileSize":123,"evidenceType":"POLICY"}'
bc "/packages/$PKG/evidence/$EV/confirm" -X POST # after the PUT to uploadUrl
bc "/packages/$PKG/evidence/$EV/links" -X POST -H "Content-Type: application/json" \
-d '{"objectiveId":"'"$OBJ"'"}' # link evidence
bc "/packages/$PKG/poams" # POA&Ms
bc "/packages/$PKG/poams" -X POST -H "Content-Type: application/json" -d '{…}'
bc "/packages/$PKG/poams/$POAM" -X PATCH -H "Content-Type: application/json" -d '{"status":"CLOSED"}'
bc "/packages/$PKG/assets" # inventory
bc "/packages/$PKG/conmon" # review freshness
bc "/packages/$PKG/ssp" # SSP sections
bc "/packages/$PKG/sprs" # score, blockers, POA&M-eligible
curl -s "$BEDROCK_CMMC_API/openapi.json" | jq '.paths | keys' # the whole surface, no auth