Skip to content
Foxx Cyberfoxxcyber/docs

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:write

Click 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 on

List 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 Control

Two 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" }' | jq

Open 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_…'
claude

Then 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.pdf

Upload 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.b means 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()  # stdio

Register 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_TOKEN in the shell (or a secret manager that injects it). Never put the literal token in CLAUDE.md, .mcp.json, a script, or a commit — add .env to .gitignore if 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 *:read and 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

Last updated September 2, 2026