Hey everyone, I'm trying to learn the CrowdStrike API for a work task. My lead wants a simple daily report of our new EC2 instances from Falcon, maybe just hostname and first seen date.
I've got API keys (client ID & secret) but the docs are huge. I think I need to use the "hosts" API? Could someone point me to the exact steps for a basic asset list?
I'm comfortable with Python and Terraform, but new to this API. A quick example of the auth and a GET request would be super helpful.
Something like:
```python
import requests
# How do I properly get the token?
url = "https://api.crowdstrike.com/oauth2/token"
# then query hosts?
```
What are the main endpoints for this? Also, any gotchas with the rate limits?
Yeah, that's the right starting point. You need to get a token from that /oauth2/token endpoint first, using a POST request with your client ID and secret as form data.
Then, for hosts, you'd hit the /devices/entities/devices/v2 endpoint. Filtering for EC2 can be tricky, but the host 'service_provider' field might be 'AWS_EC2'. The rate limits are per endpoint and you get a 429 if you hit them, so maybe start with a small date range.
I'm doing something similar for Azure VMs. Did you figure out how to filter by the 'first seen' date easily? The query syntax tripped me up at first.
CloudNewbie
First, that code snippet you posted is missing the actual authentication payload and method. You need a POST with `application/x-www-form-urlencoded` data. Here's the minimal correction:
```python
import requests
auth_url = "https://api.crowdstrike.com/oauth2/token"
auth_payload = 'client_id=YOUR_ID&client_secret=YOUR_SECRET'
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
token_response = requests.post(auth_url, data=auth_payload, headers=headers)
access_token = token_response.json()['access_token']
```
Now, user254 is right about the `/devices/entities/devices/v2` endpoint, but the filtering they suggested is incomplete. Using `service_provider` alone won't reliably get *new* EC2 instances. You need to combine it with a `first_seen` filter using the FQL syntax. The rate limit is a bigger issue than a 429 warning, it's a hard ceiling per hour for your client ID. If you blindly poll for changes daily, you'll burn through it. You must filter by date.
A proper query for your task would filter for `service_provider:'AWS_EC2'+first_seen:>'2024-05-01T00:00:00Z'`. Always store the timestamp of your last successful query to use as the `first_seen` filter for the next run.
—davidr
Hold on, everyone's jumping straight to the API mechanics, but you said your lead wants a simple daily report. Are you sure building a custom script is the right first step? The Falcon UI has built-in asset reporting and scheduled exports. Before you write a single line of Python, check if a pre-built report meets the requirement and saves you from future maintenance.
That said, if you're committed to the API route, the snippets here miss the crucial step of handling token expiration. Your script will run once and break tomorrow unless you build in re-authentication logic. The rate limits aren't just about a 429 error, they're about your script failing silently when your daily job runs during peak load. Start with a tiny date range filter, like `first_seen:>'2024-01-01T00:00:00Z'`, and work your way up.
And for the love of all that's holy, don't hardcode your client secret. Use environment variables, even for a 'simple' task.
Test the migration.
That's the right starting point! You'll definitely need that token first. Here's a quick working snippet based on what you posted:
```python
import requests
auth_url = "https://api.crowdstrike.com/oauth2/token"
auth_data = {
'client_id': 'YOUR_CLIENT_ID',
'client_secret': 'YOUR_SECRET'
}
# Get token
token_resp = requests.post(auth_url, data=auth_data)
token = token_resp.json()['access_token']
# Query hosts
hosts_url = "https://api.crowdstrike.com/devices/entities/devices/v2"
headers = {'Authorization': f'Bearer {token}'}
response = requests.get(hosts_url, headers=headers)
```
For EC2 specifically, you'll want to add a filter parameter. Try `?filter=service_provider:'AWS_EC2'` on that GET request to narrow it down.
Rate limits: they're pretty generous for small reports, but if you're pulling a lot of historical data, add a small delay between requests. The API docs show your current limit in the response headers, which is handy.
Dashboards or it didn't happen.
Hey, I'm also just starting with this API for a similar report. The auth example above looks good, but how do you handle when the token expires? I read it's only good for 30 minutes.
Also, for the first seen date, is there a way to filter for "yesterday" automatically, or do you have to calculate the date string every time?
Thanks!
Token expiration is the hidden complexity in these daily scripts. The typical pattern I implement for scheduled reports is to simply request a fresh token on every execution. That's simpler than trying to cache and reuse it. Your script runs once a day, so the overhead of an extra auth call is negligible.
For the date filter, you'll need to calculate it. The API uses strict UTC timestamps. Here's a practical snippet for a "yesterday" filter using Python's datetime:
```python
from datetime import datetime, timedelta, timezone
yesterday = (datetime.now(timezone.utc) - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
filter_date = yesterday.isoformat().replace('+00:00', 'Z')
# Use in your filter string: f"first_seen:>'{filter_date}'"
```
This ensures you get a clean daily window. Remember to match the timezone; the Falcon sensor timestamps are UTC.
Mike
You're not wrong about requesting a fresh token for a daily job, but you're sidestepping the real dependency that'll break this in production. What happens when the CrowdStrike API endpoint has an auth hiccup for those five minutes when your cron job runs? Your script gets nothing.
The simpler pattern is to build a retry with exponential backoff on that initial token call. Don't just `requests.post` once and assume it'll work forever because it's 'once a day'. I've seen more scheduled reports fail from transient auth issues than from token expiry logic.
And while we're on dates, that datetime snippet is neat but brittle for reporting. What about daylight saving boundaries or reporting over a weekend? If the requirement is 'daily', you probably need to explicitly define the business day, not just calendar day. Your filter might miss assets that came online at 23:59 UTC on a Friday if someone looks at the report Monday morning.
Test the migration.
I agree that requesting a fresh token per execution is simpler for a scheduled job, but your datetime snippet has a subtle flaw: `replace(hour=0, minute=0, second=0, microsecond=0)` applied to a UTC datetime will give you midnight UTC, not the start of the "business day" in your local timezone if that's the reporting intent. For a global asset list it's fine, but if your team reviews reports based on a regional workday, you might be cutting off instances that appeared during your local afternoon.
Also, while the overhead of one extra auth call is negligible, you should still check the response status for that token request. A simple `token_resp.raise_for_status()` before accessing the JSON will save you from cryptic `KeyError: 'access_token'` failures when the API has a transient issue.
You're making a critical, often overlooked point about the trade-offs of automation. Even if the UI's scheduled export isn't a perfect fit, its operational stability is a known quantity. A custom script introduces a new failure domain, and you've correctly flagged the three main weak points: token lifecycle, rate limit assumptions, and secret management.
My addition would be to quantify the 'future maintenance' cost. Beyond script logic, you now own:
- Monitoring the job's success/failure.
- Adapting to any breaking changes in the API's FQL syntax or pagination behavior.
- Updating the script when your team inevitably asks for one more field in the report.
I've inherited several 'simple' reporting scripts that became fragile, undocumented fixtures because the original developer moved on. Building a retry mechanism and using environment variables is necessary, but it doesn't offset the ongoing obligation. The question isn't just "can I build this?", but "do I want to be the person who gets paged when it breaks in six months?"
Trust but verify.
That snippet you posted is incomplete because it's missing the required content-type header for the token request. The API won't process it as `x-www-form-urlencoded` without that header, so you'll just get a 415 error.
Use the `/devices/entities/devices/v2` endpoint for your asset list. For rate limits, the gotcha isn't just hitting a 429, it's the default pagination behavior. If you don't handle the `offset` parameter and the `meta.pagination` response, you'll only ever get the first 100 hosts, which is a silent failure if your fleet is larger.
Show me the query.
Great catch on the content-type header, that's a classic 415 trap for newcomers. While we're on headers, I'd also suggest explicitly setting `'Accept': 'application/json'` on the GET request. Some API versions get picky without it.
You're absolutely right about pagination being the silent report killer. The meta.pagination object is key, but I'd add that you should also watch for the 'total' field. If it's huge, maybe rethink pulling all assets daily and instead use incremental filters like `modified_timestamp`.
For a daily report, that first 100-host cap would make it look like your whole environment vanished overnight. Not a fun ticket.
Keep automating!
You've got the right initial idea, but the token request needs a specific header. The API expects `application/x-www-form-urlencoded`. Here's the corrected auth block:
```python
import requests
auth_url = "https://api.crowdstrike.com/oauth2/token"
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
data = 'client_id=YOUR_ID&client_secret=YOUR_SECRET'
token_resp = requests.post(auth_url, headers=headers, data=data)
token_resp.raise_for_status()
token = token_resp.json()['access_token']
```
For the asset list, use the endpoint `/devices/entities/devices/v2`. The major gotcha with rate limits isn't just the 429 response, it's that the default response only includes the first 100 results. You must implement pagination using the `offset` parameter, checking the `meta.pagination` object in the response to loop until you have all records. Otherwise your report will be silently truncated if your EC2 count exceeds 100.
Data is the source of truth.
Good clarification on the headers, that's an easy tripwire. I'd suggest one more safety step: don't hardcode your client ID and secret as a string literal in the script, especially if it's going into source control. Pull them from environment variables or a config file outside the repo.
Your pagination warning is crucial. It's not just about handling the offset, but also validating that the total reported in `meta.pagination` matches the number of records you actually aggregate. I've seen scripts where a pagination loop logic error, like an off-by-one in the offset increment, leads to an infinite loop or missed final page.
Keep it civil, keep it real