
14 min read
What the DEVASC v1.0 Skills Assessment Asks You to Build
The DEVASC v1.0 Skills Assessment is the final hands-on task in Cisco’s DevNet Associate course: you complete a starter Python file called devasc-sa.py so a Webex Teams bot watches one room, reads a command posted there, calls two public web services, and posts the answer back into that room.
You get a scenario document with the file. Part 1 is documentation research, Part 2 is the code, and there is not one multiple-choice question in either half.
Search also carries it as the devasc final skills assessment. Nothing is graded until the program runs, so every mark comes from working code.
What does a skills assessment include?
In the Cisco NetAcad sense, three parts: a scenario document describing a system and listing the steps, a starter file with the graded pieces left blank, and a deliverable you hand to your instructor. No question bank, no answer sheet. The document is the specification, the blanks are the work, the artefact is the answer.
Two sittings, three accounts, and one file to submit
Plan two sittings. Part 1 is reading, and it moves fast when the documentation links still resolve. Part 2 is where the evening goes, mostly on setup rather than Python, because three accounts have to issue working credentials before one line of your code can be tested. The deliverable is one file, plus a screenshot of the room if your handout asks.

How the DEVASC v1.0 Skills Assessment Is Structured: Part 1 and Part 2
The split looks like busywork followed by the real thing, and that reading is what strands people. Part 1 asks six documentation questions, Part 2 has fourteen coding steps, and the six answers from Part 1 are the values Part 2 asks you to type in.
Part 1: the six lookups that become your code
Read them as a shopping list for Part 2.
- The Webex token and its lifetime. A personal access token expires twelve hours after you sign in.
- Listing rooms:
GET https://webexapis.com/v1/rooms. - Reading and creating messages, both at
https://webexapis.com/v1/messages: a GET with a room filter, a POST with a body. - The latitude and longitude keys in the geocoding response, nested several levels deep. This is the step people guess at, so here is the answer outright. In MapQuest the pair sits at
results[0]["locations"][0]["displayLatLng"], an object holdinglatandlng. In Graphhopper, where the lookup runs in reverse, the place name you want ishits[0]["name"]. - The ISS endpoint, its parameters, and its response keys.
- How Python turns seconds since the epoch into something readable. Answered outright:
time.ctime(), from the standard librarytimemodule. Hand it the integer and it hands back a string likeSat Jun 20 20:18:29 2020.
Every blank in Part 2 has a matching answer in Part 1, so a blank that stumps you is naming the lookup you skipped.
Part 2: fourteen steps, and only four of them are thinking
They group into five jobs, and the walkthrough below heads each group with its own step numbers so you can jump straight to the one you are stuck on. Steps 1 and 2 handle imports and the token, 3 to 5 are Webex plumbing, 6 to 8 the poll loop and the command it reads, 9 to 11 the two services and the timestamp, 12 to 14 the sentence, the post and the run. Most devasc skills assessment questions that reach a forum are about steps 8, 10 and 14, all three parsing problems rather than Python ones.
Which Version of the DEVASC v1.0 Skills Assessment You Have: MapQuest or Graphhopper
Check this before following any walkthrough, including this one. The lab ships in at least two variants, and they do not differ by a swapped service name. They run in opposite directions, so a student on the wrong guide hits step 8 with a response that does not match their screen.
| MapQuest version | Graphhopper version | |
|---|---|---|
| What you post in the room |
/Austin, Texas, a place name |
/5, a number of seconds |
| What the bot does with it | Place to coordinates, then when the ISS next passes over that spot | Waits, reads where the ISS is now, then coordinates to place |
| Geocoding direction | Forward | Reverse |
| ISS endpoint named | Pass times for a location | Current position |
| The reply it posts | “In Austin, Texas the ISS will fly over on … for … seconds.” | The place the ISS is above right now |
| Does that endpoint still answer? | No, 404 | Yes |
One version of this lab can still be finished against live services and the other cannot, and no result on the first page of this search says so.
A thirty-second check on your handout tells you which version you have
Go to Part 1 of your handout and read the step naming the geocoding service. MapQuest means the flyover-forecast version. Graphhopper means the where-is-it-now version, and your command is a bare number rather than a city. The Webex half is identical either way.
Before you copy anything: a walkthrough for the other variant looks almost right. Imports and Webex calls match, and the break shows up at the parsing step, exactly where a student assumes the mistake is theirs.

The Fifteen Minutes Before the DEVASC v1.0 Skills Assessment That Save an Evening
Most of the time lost here is lost to setup. Three services have to be reachable with valid credentials before any of your code means anything. A failure there announces itself; the same failure inside a finished script arrives as a stack trace pointing at your parsing.
How to prepare for a skills assessment?
The general answer holds for every hands-on assessment: make the environment reproducible, read the specification twice, then write. Here that means booting the DEVASC virtual machine, signing in to each service, and calling each endpoint once from the command line before you open the editor. If a call works there and fails in your script, the problem is in your own fifteen lines.
What to have open before you type: the VM, the tokens, and the API keys
-
The DEVASC VM, running, with network access. The image is not a public download: it is an OVA handed out inside the NetAcad course itself, in the lab that installs the virtual machine lab environment, and you import it into VirtualBox rather than fetch it from a link someone posted in a forum. A failed boot never looks like Python. VirtualBox either refuses outright with a message about VT-x or AMD-V being unavailable, which means hardware virtualization is switched off in your machine’s firmware settings and nothing about the image is wrong, or the window sits on a black screen and never reaches the
devasclogin prompt, which is usually too little memory assigned or a half-finished import. If you cannot get it running tonight, this particular lab does not need it:devasc-sa.pyis three HTTPS calls, so Python 3 andpip install requestson your own machine run the same file. Ask your instructor before submitting from outside the VM, because that is a marking decision rather than a technical one. Every other devasc vm skills assessment setup problem worth debugging shows up as a failed call, not a Python error. - A Webex identity and a token. A personal access token is quick and dies in twelve hours. A bot’s token is described in Cisco’s own bot documentation as non-expiring, which suits a lab you come back to, and it changes how the bot behaves in a group space.
- A geocoding key from whichever service your handout names. Both issue a free developer key.
- One test call each, by hand. Three good responses before line one.
A failed call at the command line names its own problem, while the same failure inside finished code looks like a bug in your Python.
Walking Through the DEVASC v1.0 Skills Assessment, Step by Step
Each step below comes with the reason it exists, and the reason is the part worth keeping. The exam behind this lab tests whether you understand how a REST call is authenticated and parsed, not whether you can paste a file. The fourteen steps are grouped below by number, Steps 1-2, Steps 3-5, Steps 6-8, Steps 9-11 and Steps 12-14, and the whole file follows both groups in one block per variant.
Can you give me an example of a skills assessment test?
This lab is the example: a specification with blanks, three unrelated web services, and one sentence that has to appear in a chat room at the end. There is nothing to recite. The test is whether you can read unfamiliar documentation and turn it into a call that returns what you expect.
Steps 1-2: the imports and the access token
Step 1 is three imports and step 2 is the token, and both variants are identical here. requests makes every call, time does two separate jobs later on (the wait and the timestamp), and json earns its place the first time you print a response to see what came back.
import json
import time
import requests
accessToken = "PASTE_YOUR_WEBEX_TOKEN_HERE"
WEBEX = "https://webexapis.com/v1"
headers = {
"Authorization": "Bearer " + accessToken.strip(),
"Content-Type": "application/json",
}
The .strip() is not tidiness. A token pasted out of a browser almost always carries a trailing newline, and Webex answers that with a 401 that reads exactly like an expired token.
Steps 3-5: listing Webex rooms and capturing the room ID
Steps 3 to 5 exist so you can find the identifier of the room to watch. A room’s title is not its identifier, and passing a title where the API wants an ID is where people first stall.
r = requests.get(WEBEX + "/rooms", headers=headers)
if r.status_code != 200:
raise SystemExit("rooms call failed " + str(r.status_code) + ": " + r.text)
for room in r.json()["items"]:
print(room["type"], "|", room["title"], "|", room["id"])
roomIdToGetMessagesFrom = input("Paste the room ID to watch: ").strip()
The status-code check is not decoration. It is the difference between an error you can read and a KeyError forty lines later.
Steps 6-8: the poll loop, and pulling the command out of the message text
This is the half most posted walkthroughs leave as a comment, and it is the half that makes the file a bot rather than a script. Step 6 opens a loop that never ends on its own, step 7 asks Webex for the newest message in that one room, and step 8 decides whether that message is a command meant for you.
myPersonId = requests.get(WEBEX + "/people/me", headers=headers).json()["id"]
lastHandledId = None
while True:
m = requests.get(WEBEX + "/messages",
headers=headers,
params={"roomId": roomIdToGetMessagesFrom, "max": 1})
items = m.json().get("items", [])
if not items:
time.sleep(1)
continue
message = items[0]
if message["personId"] == myPersonId or message["id"] == lastHandledId:
time.sleep(1)
continue
lastHandledId = message["id"]
text = message.get("text", "").strip()
if not text.startswith("/"):
continue
What happens to text next is the one place the two variants split. MapQuest treats everything after the slash as a place name, so text[1:].strip() turns /Austin, Texas into Austin, Texas. Graphhopper treats it as a count of seconds, and then actually waits:
try:
waitSeconds = int(text[1:].strip())
except ValueError:
continue
time.sleep(waitSeconds)
That time.sleep(waitSeconds) is the line the Graphhopper handout is graded on, and no answer key written for the MapQuest variant contains it. The try around int() is what stops a stray /hello ending an evening that had been running fine.
Steps 9-11: geocoding, the ISS call and the epoch timestamp
Steps 9 to 11 are one job in two halves: coordinates, then a time. In the MapQuest variant the coordinates come first.
geo = requests.get(
"https://www.mapquestapi.com/geocoding/v1/address",
params={"key": mapQuestKey, "location": location})
ll = geo.json()["results"][0]["locations"][0]["displayLatLng"]
lat, lng = ll["lat"], ll["lng"]
ISS = "http://api.open-notify.org"
iss = requests.get(ISS + "/iss-pass.json",
params={"lat": lat, "lon": lng})
first = iss.json()["response"][0]
when = time.ctime(first["risetime"])
duration = first["duration"]
In the Graphhopper variant the order reverses: read the position, then name it.
ISS = "http://api.open-notify.org"
now = requests.get(ISS + "/iss-now.json").json()
lat = now["iss_position"]["latitude"]
lng = now["iss_position"]["longitude"]
geo = requests.get("https://graphhopper.com/api/1/geocode",
params={"point": "{},{}".format(lat, lng),
"reverse": "true", "key": graphHopperKey})
hits = geo.json()["hits"]
where = hits[0]["name"] if hits else "open ocean"
Two details decide the devasc skills assessment epoch timestamp conversion step. risetime is an integer count of seconds since the epoch, and printing it raw is a lost mark. And Python’s documentation for time.ctime says plainly that it returns local time. Two students with identical code see different timestamps when their machines sit in different time zones, and both are correct. The hits guard matters because the ISS spends most of its orbit over water, where reverse geocoding returns nothing.
Steps 12-14: the sentence, the post, and the loop that keeps the bot alive
Step 12 formats the sentence, step 13 posts it, graded as written in your handout with the punctuation included, and step 14 is the single line that sends the loop back round to wait for the next command.
responseMessage = "In {} the ISS will fly over on {} for {} seconds.".format(
location, when, duration)
requests.post(WEBEX + "/messages",
headers=headers,
json={"roomId": roomIdToGetMessagesFrom, "text": responseMessage})
time.sleep(1)
That closing time.sleep(1) is not the Graphhopper wait. It is the pause between polls, and leaving it out is how a finished lab starts collecting 429 responses from Webex within a minute of starting.

The complete devasc-sa.py, one block per variant
Here is the whole file, twice, so you are not assembling it from fragments. Both run as written on Python 3 once you paste your own token and key into the two lines near the top. Nothing is abbreviated, every variable is defined before it is used, and the numbered comments map back to the step headings above. Take the block that matches your handout, not the one that looks familiar from a forum.
Variant A, MapQuest. You post a place name, the bot answers with the next flyover. Watch the ISS_PASS line: that endpoint is retired, and the next section gives you the swap and the honest limits of it.
# devasc-sa.py -- Variant A, MapQuest. Post a place name, get the next ISS flyover.
# Steps 1 to 14. Runs on Python 3 after: pip install requests
# --- Step 1: imports ---------------------------------------------------------
import json
import time
import requests
# --- Step 2: credentials and endpoints. Paste your own into the top two. -----
accessToken = "PASTE_YOUR_WEBEX_TOKEN_HERE"
mapQuestKey = "PASTE_YOUR_MAPQUEST_KEY_HERE"
WEBEX = "https://webexapis.com/v1"
MAPQUEST = "https://www.mapquestapi.com/geocoding/v1/address"
ISS_HOST = "http://api.open-notify.org"
ISS_PASS = ISS_HOST + "/iss-pass.json" # retired; see the swap below
headers = {
"Authorization": "Bearer " + accessToken.strip(),
"Content-Type": "application/json",
}
# --- Steps 3 to 5: list the rooms, print them, capture the one to watch ------
r = requests.get(WEBEX + "/rooms", headers=headers)
if r.status_code != 200:
raise SystemExit("rooms call failed " + str(r.status_code) + ": " + r.text)
for room in r.json()["items"]:
print(room["type"], "|", room["title"], "|", room["id"])
roomIdToGetMessagesFrom = input("Paste the room ID to watch: ").strip()
# Your own person ID, so the bot never answers its own posts.
myPersonId = requests.get(WEBEX + "/people/me", headers=headers).json()["id"]
lastHandledId = None
# --- Steps 6 to 14: the run loop --------------------------------------------
while True:
# Step 6 and 7: ask for the newest message in that one room.
m = requests.get(WEBEX + "/messages",
headers=headers,
params={"roomId": roomIdToGetMessagesFrom, "max": 1})
if m.status_code != 200:
print("messages call failed", m.status_code, m.text)
time.sleep(5)
continue
items = m.json().get("items", [])
if not items:
time.sleep(1)
continue
message = items[0]
if message["personId"] == myPersonId or message["id"] == lastHandledId:
time.sleep(1)
continue
lastHandledId = message["id"]
# Step 8: pull the command out of the message text.
text = message.get("text", "").strip()
if not text.startswith("/"):
continue
location = text[1:].strip() # "/Austin, Texas" -> "Austin, Texas"
if not location:
continue
print("Location:", location)
# Step 9: forward geocode. This key path is Part 1 item 4.
geo = requests.get(MAPQUEST, params={"key": mapQuestKey, "location": location})
results = geo.json().get("results", [])
if not results or not results[0]["locations"]:
print("no match for", location)
continue
ll = results[0]["locations"][0]["displayLatLng"]
lat, lng = ll["lat"], ll["lng"]
print("GPS coordinates: {}, {}".format(lat, lng))
# Step 10: the next pass over those coordinates.
iss = requests.get(ISS_PASS, params={"lat": lat, "lon": lng, "n": 1})
if iss.status_code != 200:
print("ISS pass-times endpoint returned", iss.status_code, "- see the swap below")
time.sleep(1)
continue
first = iss.json()["response"][0]
# Step 11: epoch seconds into something readable. Part 1 item 6.
when = time.ctime(first["risetime"])
duration = first["duration"]
# Step 12: the sentence, worded exactly as your handout words it.
responseMessage = "In {} the ISS will fly over on {} for {} seconds.".format(
location, when, duration)
print("Sent:", responseMessage)
# Step 13: post it back into the same room.
requests.post(WEBEX + "/messages",
headers=headers,
json={"roomId": roomIdToGetMessagesFrom, "text": responseMessage})
# Step 14: round again. The pause keeps you inside Webex rate limits.
time.sleep(1)
Variant B, Graphhopper. You post a number, the bot waits that many seconds, reads where the ISS is, and names the place underneath it. Every service this one calls still answers.
# devasc-sa.py -- Variant B, Graphhopper. Post /N, wait N seconds, name where the ISS is.
# Steps 1 to 14. Runs on Python 3 after: pip install requests
# --- Step 1: imports ---------------------------------------------------------
import json
import time
import requests
# --- Step 2: credentials and endpoints. Paste your own into the top two. -----
accessToken = "PASTE_YOUR_WEBEX_TOKEN_HERE"
graphHopperKey = "PASTE_YOUR_GRAPHHOPPER_KEY_HERE"
WEBEX = "https://webexapis.com/v1"
GRAPHHOPPER = "https://graphhopper.com/api/1/geocode"
ISS_NOW = "http://api.open-notify.org/iss-now.json" # still answers 200
headers = {
"Authorization": "Bearer " + accessToken.strip(),
"Content-Type": "application/json",
}
# --- Steps 3 to 5: list the rooms, print them, capture the one to watch ------
r = requests.get(WEBEX + "/rooms", headers=headers)
if r.status_code != 200:
raise SystemExit("rooms call failed " + str(r.status_code) + ": " + r.text)
for room in r.json()["items"]:
print(room["type"], "|", room["title"], "|", room["id"])
roomIdToGetMessagesFrom = input("Paste the room ID to watch: ").strip()
myPersonId = requests.get(WEBEX + "/people/me", headers=headers).json()["id"]
lastHandledId = None
# --- Steps 6 to 14: the run loop --------------------------------------------
while True:
# Step 6 and 7: ask for the newest message in that one room.
m = requests.get(WEBEX + "/messages",
headers=headers,
params={"roomId": roomIdToGetMessagesFrom, "max": 1})
if m.status_code != 200:
print("messages call failed", m.status_code, m.text)
time.sleep(5)
continue
items = m.json().get("items", [])
if not items:
time.sleep(1)
continue
message = items[0]
if message["personId"] == myPersonId or message["id"] == lastHandledId:
time.sleep(1)
continue
lastHandledId = message["id"]
# Step 8: pull the number out of the message text. "/5" -> 5.
text = message.get("text", "").strip()
if not text.startswith("/"):
continue
try:
waitSeconds = int(text[1:].strip())
except ValueError:
continue
print("Waiting", waitSeconds, "seconds before reading the ISS position")
# Step 9: the wait itself. This is the graded line every MapQuest answer key skips.
time.sleep(waitSeconds)
# Step 10: where the ISS is right now.
now = requests.get(ISS_NOW).json()
lat = now["iss_position"]["latitude"]
lng = now["iss_position"]["longitude"]
print("GPS coordinates: {}, {}".format(lat, lng))
# Step 11: reverse geocode those coordinates, and the timestamp. Part 1 items 4 and 6.
geo = requests.get(GRAPHHOPPER, params={
"point": "{},{}".format(lat, lng),
"reverse": "true",
"key": graphHopperKey,
})
hits = geo.json().get("hits", [])
where = hits[0]["name"] if hits else "open ocean"
stamp = time.ctime(now["timestamp"])
# Step 12: the sentence, worded exactly as your handout words it.
responseMessage = "The ISS is over {} ({}, {}) as of {}.".format(
where, lat, lng, stamp)
print("Sent:", responseMessage)
# Step 13: post it back into the same room.
requests.post(WEBEX + "/messages",
headers=headers,
json={"roomId": roomIdToGetMessagesFrom, "text": responseMessage})
# Step 14: round again, ready for the next command.
time.sleep(1)

Where the DEVASC v1.0 Skills Assessment Breaks, and What Each Failure Looks Like
Every failure below has a symptom, a cause and a fix, and none is a Python mistake. The lab was written around 2020 and the services it calls have moved since, so correct code fails for reasons you did not write.
The ISS pass-times endpoint was removed, and that is not your code
Checked live on 7 September 2026: a GET to api.open-notify.org/iss-pass.json returns 404 Not Found, and the service’s own page for that endpoint now reads, in full, “This api has been removed.” The host is fine and iss-now.json still answers 200 with a live latitude and longitude. That one path is why the Graphhopper variant runs end to end and the MapQuest variant cannot.
The replacement worth naming is Where the ISS at?, documented at wheretheiss.at/w/developer. Its position endpoint is https://api.wheretheiss.at/v1/satellites/25544, where 25544 is the ISS catalogue number, and it needs no key and no sign-up. On the same check, 7 September 2026, it answered 200 with a flat object rather than the nested one Open Notify returns:
{"name": "iss", "id": 25544,
"latitude": 41.986065432553,
"longitude": -75.581583933459,
"altitude": 420.04725112229,
"velocity": 27599.495044922,
"visibility": "eclipsed",
"timestamp": 1788767427,
"units": "kilometers"}
Three lines swap it in wherever your file calls iss-now.json. The only thing that changes downstream is that latitude and longitude sit at the top level instead of inside iss_position, and they arrive as numbers rather than strings:
ISS_NOW = "https://api.wheretheiss.at/v1/satellites/25544" now = requests.get(ISS_NOW).json() lat, lng = now["latitude"], now["longitude"]
Be straight with yourself about what that fixes. It restores the current position cleanly, which is all the Graphhopper variant ever needed. It does not restore pass predictions, because no free service publishes those any more. The same host will return the ISS position at any list of moments you hand it, ?timestamps=1788767400, which also answered 200 on that check, so a flyover check for the MapQuest variant becomes a loop of your own across the next ninety minutes rather than one call. Writing that reasoning into the file is the work the lab was asking for in the first place.
Three good options if your handout names the pass-times endpoint. Point the call at the Where the ISS at? position endpoint above and comment what you changed and why. Or test your parsing against a saved copy of the documented response. Or ask your instructor. Whichever you pick, write it in the file: a marker reading “this endpoint was retired, here is what I did instead” is reading better work than a silent failure.
A 401 from Webex, and the room ID that is not the room name
The 401 arrives with its own diagnosis attached. Webex returns {"message": "The request requires a valid access token set in the Authorization request header."}, and there are three ways to earn it: the token expired, which for a personal access token is twelve hours after you signed in; Bearer and its single space are missing; or the paste carried a trailing newline, which is what .strip() above is for. The quieter half is a 200 with an empty items list, meaning you passed the room’s title where the API wanted the long opaque ID.
Stopping the bot from answering its own messages
Two opposite shapes, decided by the token you chose. With a personal access token the messages list includes your own posts, so the script reads its own reply on the next pass and loops. With a bot token in a group space the room looks dead instead, because Cisco’s bot documentation states that in group rooms bots only see messages in which they are mentioned. Both are fixed by knowing who you are and where you got to:
me = requests.get("https://webexapis.com/v1/people/me",
headers=headers).json()["id"]
for m in messages["items"]:
if m["personId"] == me:
continue
if m["id"] == lastHandledId:
break
A bot that answers itself is an identity problem, and one comparison against your own person ID ends it. For the mention case, move the bot into a one-to-one space or prefix commands with its name.

How to Check Your DEVASC v1.0 Skills Assessment Before You Submit It
Your instructor holds the official marking scheme and this is not it, but every item below is something a marker sees in a minute.
Seven things a marker can check in thirty seconds
- The program runs from a clean terminal with no edits first.
- Every blank is filled, with no
<placeholder>text left in the file. - The rooms loop prints type, title and ID, and your room is identifiable.
- The bot answers a command posted after it started, and never itself.
- Every request checks its status code and says something useful on failure.
- The timestamp is converted from epoch, not left as a ten-digit number.
- The reply matches the sentence in your handout, punctuation included.
This is the expected terminal output as the MapQuest handout documents it, four lines for a single command, with the posted sentence wrapped across the last two:
Location: Austin, Texas
GPS coordinates: 30.264979, -97.746598
Sent: In Austin, Texas the ISS will fly over on
Sat Jun 20 20:18:29 2020 for 645 seconds.
Read it as the handout’s documented sample rather than as a rule about line counts. Your coordinates and your date will differ, the wrap depends on your terminal width, and the Graphhopper handout documents a different sample entirely. The line that is actually marked is the last one, because that is the sentence the bot posts into the room.
If items 4, 5 and 6 pass, the parts of this lab the certification cares about are already done. That is a grading rubric you can hold against your own screen tonight.
Using an answer key to learn the build, not to hand one in
Finished answer keys for the devasc v1.0 skills assessment are easy to find, and some of them sit behind a paywall. A downloaded solution passes this lab and fails the exam behind it, which asks how a bearer token authenticates a request and how a nested JSON response is walked. Read a solution the way you read a colleague’s pull request: find the line you could not have written, work out why it is there, close the tab, write your own.
How the DEVASC Skills Assessment Maps to the 200-901 Exam
Finishing the lab and passing the certification are different bars. Cisco publishes the 200-901 exam topics with weights, so the comparison is arithmetic.
| 200-901 domain | Weight | Does this lab exercise it? |
|---|---|---|
| Understanding and using APIs | 20% | Yes. Three REST calls, bearer auth, nested JSON, error handling |
| Software development and design | 15% | Partly. One script, structured and run |
| Cisco platforms and development | 15% | Partly. Webex only |
| Infrastructure and automation | 20% | No |
| Application deployment and security | 15% | No |
| Network fundamentals | 15% | No |
What the lab exercises, and the three domains it never touches
The lab lands hard on one domain worth a fifth of the exam and leaves half the blueprint untouched. Infrastructure and automation, application deployment and security, and network fundamentals carry half the weighting between them, and none appears in devasc-sa.py. That is your study list, in that order. Our walkthrough of the CCNA Cybersecurity Operations v1.0 skills assessment covers a different Cisco lab in the same family, and the CCNA 1 practice skills assessment is where network fundamentals gets exercised.
How long is the skill assessment valid for?
The assessment carries no validity date. It is coursework and it counts once, which is the answer for anyone who arrived after reading about migration assessments that do expire. The certification is different: Cisco lists it as valid for three years, after which you recertify by exam or by continuing education credits. Worth knowing before you plan around it, Cisco now presents this certification as CCNA Automation, exam 200-901 CCNAAUTO. Same number, same blueprint, new name. For the general case, we have a piece on how long a skills assessment stays valid.

The Bot Runs. Now What Do You Point the Certification At?
A finished devasc-sa.py is evidence of one narrow, useful thing. Handed a specification and three sets of documentation you had never opened, you got them to cooperate in an evening, on your own. The lab checks that, and it checks it well.
Year one of automation work looks a lot like the lab. Year five looks different: other people’s scripts, change windows at eleven at night, an on-call rotation, long stretches of reading logs for one wrong assumption. Some people find that steadying and stay twenty years. Others find the same week flattens them by Wednesday, and a working bot cannot tell them which they are.
That second question is a measurement too, and it is the one Pigment answers: which weeks a person can keep doing for years, not only which tasks they are able to finish. A career comes apart on the second question, not the first, usually in year five and long after the certification that started it.
Year one is the lab. Year five is the on-call rotation.
The practical version is not a career change. It is choosing which direction inside automation to point the certification you just earned, because a network reliability role, a platform engineering role and an integration developer role are three different weeks wearing one job family. An eleven-at-night change window and a week of reading logs for one wrong assumption either settle you or empty you, and which one it is decides that fork. Answering that now beats leaving the choice to whoever posts the first opening. Our read on which IT career fits walks the same fork.
Understand how you work, then aim the certification
An evening with three APIs shows what you can build. A working year asks the other question, and Pigment’s Career Self-Discovery Assessment is where you answer it.
See what fits you →DEVASC Skills Assessment: Questions People Also Ask
Can I pass a skill assessment in Vetassess?
You can, but it is unrelated and Google mixes it into this result set. VETASSESS assesses overseas qualifications for Australian skilled migration, with no connection to Cisco, DevNet or this lab.
Does the skills assessment replace the DevNet Associate final exam?
No. The skills assessment is coursework marked by your instructor. The certification comes from the separate 200-901 proctored exam, now listed as CCNA Automation, covering six domains this lab partly touches.
Where does the difficulty sit, in the Python or in the plumbing?
In the plumbing. The Python is beginner level: three GET requests, one POST, a loop and a format string. The time goes on tokens, keys, room identifiers, nested response keys, and one retired service.
Do the answers posted online match my handout?
Not always, and this is the trap. The posted solutions are written for the MapQuest variant, where you post a place name. If your handout names Graphhopper, your bot takes a number of seconds and reverse geocodes the ISS position, so everything from step 8 parses differently.
What if my instructor never gave me devasc-sa.py?
Ask for it first. The starter file is instructor-distributed by design, so yours matches the variant you are marked against. Copies circulate publicly, but the wrong variant costs more time than writing it from the specification.