8 days ago
i’m sending a json POST to an api at data.lacity.org to pull some data. this works fine 100% of the time when i run the app locally. however when i run it from railway, depending on the deploy, i get 403 errors:
7/20/2026, 6:31:41 PM: api: /api/v3/views/73a2-6ar5/query.json postData: {"query":"SELECT locator_gis_returned_address, casenumber, status, createddate, systemmodstamp, closeddate, resolution_code__c, reason_code__c, type, geolocation__latitude__s, geolocation__longitude__s WHERE type = 'Streetlight Repair Services' and systemmodstamp > '2025-04-01' and geolocation__latitude__s > 34.062125 and geolocation__latitude__s < 34.083554 and geolocation__longitude__s > -118.338907 and geolocation__longitude__s < 118.326518","includeSynthetic":false}
7/20/2026, 6:31:42 PM: postJson error: 403 "\r\n403 Forbidden\r\n\r\n403 Forbidden\r\nnginx\r\n\r\n\r\n"
I’m using axios to post the query. The apiToken doesn’t change.
const axionApi = axios.create({
baseURL: 'https://data.lacity.org',
responseType: 'json',
headers: {
'Content-Type' : 'application/json',
'X-App-Token' : apiToken,
'User-Agent': 'Mozilla/5.0 (Node.js LA City Query)'
}});
The explanation I’ve gotten so far is that data.lacity.org are using a WAF on nginx, and some of the railway URLs are blacklisted. I have no way to contact them about this and no idea why this would be the case. Can you suggest a solution?
1 Replies
8 days ago
This thread has been opened as a bounty so the community can help solve it.
Status changed to Open Railway • 8 days ago
2 hours ago
Your 403 body is nginx's default HTML error page. That's the detail worth pulling on — Socrata returns JSON errors for anything auth-related, including a bad, missing, or rate-limited X-App-Token. A bare nginx HTML 403 means the request was rejected at the edge, before it ever reached the Socrata application.
So your app token isn't the problem and rotating it won't help. This is IP reputation, which also explains the pattern you noticed: it varies by deploy because your outbound address varies by deploy. Some of the addresses you land on are on a blocklist, some aren't. Your instinct about the WAF was right.
Two things worth doing.
First, confirm it cheaply. Add a line at startup that hits any IP-echo endpoint and logs the result, then correlate the egress address against which deploys fail. If the failures cluster on particular addresses, that's your confirmation — and it's also exactly what you'd send to whoever can unblock it.
Second, try the classic SODA endpoint rather than the v3 query API:
https://data.lacity.org/resource/73a2-6ar5.json?$query=
I just tested that dataset and it returns 200 with no app token at all. Different path, potentially different WAF rules, and it takes ten minutes to find out whether the block is host-wide or specific to /api/v3/views/. Your SoQL carries over unchanged, so it's a cheap test.
If both paths are blocked then it's purely IP-level, and your realistic options are to route that one outbound call through a proxy on an address that isn't blocked, or to get the range allowlisted.
On contact — you do have a route. data.lacity.org is Socrata-hosted (now Tyler Technologies) and the DataLA portal has a support/feedback channel. Worth one message describing the block with the specific addresses, because a WAF rule that catches an entire cloud provider's range is almost always unintentional collateral rather than a deliberate policy aimed at you.