Error codes
This page lists errors you can hit when calling the ExpTech API. The body depends on the source: auth and quota failures have no body, OAuth2 returns JSON, and an SSE stream returns plain text or stream events. Read the status code first, then decide whether to parse the body.
HTTP status codes
Section titled “HTTP status codes”| Status | Source | Condition | Body | What to do |
|---|---|---|---|---|
| 302 | Data API | Some APIs redirect a query to its canonical form (see each endpoint) | Redirect | Follow the redirect (curl -L; fetch follows by default) |
| 304 | Data API | If-None-Match was sent and the data is unchanged |
Empty | Keep the cached copy |
| 400 | Data API | Bad parameters (each endpoint says whether it returns 400) | Depends on the endpoint | Fix the parameters |
| 400 | SSE | topics is missing, or the topic name does not exist |
Plain text | Fix the topic name |
| 400 | OAuth2 | See OAuth2 errors | JSON | Fix the request |
| 401 | HTTP APIs that require a token | No X-Api-Token, or the token is invalid or revoked |
Empty | Check the header name and the token |
| 401 | SSE | The token is invalid or revoked (invalid token); a topic that requires a permission was requested with no token (unauthorized) |
Plain text | Check the token |
| 401 | OAuth2 | Client authentication failed (JSON invalid_client); the userinfo token is invalid (empty body) |
JSON or empty | Check the client settings or the token |
| 403 | HTTP APIs that require a token | The token lacks that permission; the account is not subscribed to that resource; the server cannot look up quota data right now | Empty | Check permissions and the subscription |
| 403 | SSE | forbidden: token scope missing, forbidden: not subscribed |
Plain text | Check permissions and the subscription |
| 403 | OAuth2 | too_many_tokens: the user already has 25 tokens |
JSON | Ask the user to delete tokens they do not use |
| 404 | All | The path does not exist; the data does not exist (see each endpoint); the node you named does not serve this route (see Servers and Base URL) | Empty or an HTML page | Check the path and the host |
| 405 | SSE, permission list | A method other than GET was used | SSE: method not allowed; permission list: empty |
Use GET |
| 429 | HTTP APIs that require a token | Today’s quota is used up | Empty | Retry after 00:00 Taipei time, or raise the plan |
| 429 | SSE | A topic that requires a permission is over quota (quota exceeded) |
Plain text | Same as above; a reconnect within about 60 seconds is still rejected |
| 429 | OAuth2 | The same IP called OAuth2 endpoints too often (about 4 times per second, with a short burst of 5) | HTML page | Slow down and retry |
| 500 | OAuth2, data API | Internal server error | JSON, or whatever that endpoint returns | Retry later |
| 502, 503, 504 | All | The server or an upstream is temporarily unavailable | May be an HTML page | Switch host or retry later. See Servers and Base URL |
| 503 | SSE | The auth service is temporarily unreachable (auth service unavailable), and this request included a topic that requires a permission |
Plain text | Reconnect later |
Auth and quota failures (no body)
Section titled “Auth and quota failures (no body)”When authentication fails on an HTTP API that requires a token, the response is only a status code. There is no JSON and no WWW-Authenticate header. The conditions for each status are in Usage and quota.
Data API errors
Section titled “Data API errors”Data APIs (earthquakes, weather, map tiles, and so on) use an error format that depends on the endpoint. It is written in each page’s responses and Notes. In common:
- Many APIs do not return 400 for an invalid parameter. They ignore it or fall back to the default. Follow the page for that API.
- A 404 or 5xx can be an HTML error page, not JSON.
JSON.parseon that body fails.
OAuth2 errors
Section titled “OAuth2 errors”Errors from OAuth2 and the public info API are JSON, always in this shape:
{ "error": "invalid_grant" }These responses are zstd-compressed (Content-Encoding: zstd), the same as success responses.
error |
HTTP | Where | Condition |
|---|---|---|---|
invalid_request |
400 | token, info/oauth-client |
The form could not be parsed; client_id is missing |
invalid_client |
400 | authorize, info/oauth-client |
client_id does not exist |
invalid_client |
401 | token | client_id does not exist, or a confidential client’s client_secret is wrong |
invalid_redirect_uri |
400 | authorize | redirect_uri is not in that client’s registered list |
unsupported_grant_type |
400 | token | grant_type is not authorization_code |
invalid_grant |
400 | token | The authorization code does not exist, was already used, or is expired, or client_id, redirect_uri, or PKCE does not match |
too_many_tokens |
403 | token | The user already has 25 tokens |
server_error |
500 | token | Internal server error |
After redirect_uri is confirmed valid, errors from the authorization endpoint are not JSON. The server redirects to redirect_uri with error in the query:
error |
Condition |
|---|---|
unsupported_response_type |
response_type is not code |
invalid_request |
code_challenge is missing, or code_challenge_method is not S256 |
invalid_scope |
scope contains a permission name that does not exist |
access_denied |
The user denied the request |
server_error |
The server could not create an authorization code |
When GET /api/v1/oauth2/userinfo fails, the response is 401 with an empty body, not the JSON above.
Stream errors and disconnect reasons
Section titled “Stream errors and disconnect reasons”The strings below are the exact text the server sends. Match them exactly, or match a prefix. Stream protocol details are in real-time streams.
Connection rejected
Section titled “Connection rejected”A request that fails at connect time does not start a stream. The body is plain text (with a trailing newline).
| HTTP | Body | Condition |
|---|---|---|
| 400 | topics required, e.g. ?topics=… |
The multiplexed entry was called with no topics |
| 400 | unknown topic: <name> |
The topic name does not exist (it is case-sensitive) |
| 401 | invalid token |
A token was sent, and it is invalid or revoked. Public topics are rejected too |
| 401 | unauthorized |
A topic that requires a permission was requested with no token |
| 403 | forbidden: token scope missing |
The token does not have that topic’s permission |
| 403 | forbidden: not subscribed |
The account is not subscribed to that resource |
| 429 | quota exceeded |
That resource’s quota for today is used up |
| 503 | auth service unavailable |
The auth service is temporarily unreachable |
| 405 | method not allowed |
The method is not GET |
On the multiplexed entry (/api/v1/trem/sse?topics=), when every topic is rejected, the body appends : <topic>, for example forbidden: not subscribed: <topic>. If at least one topic is accepted, the connection succeeds (200). Rejected topics and their reasons are in the denied field of the opening event: info. The reason string is one of unauthorized, forbidden: token scope missing, forbidden: not subscribed, or quota exceeded, with no topic name appended.
Dropped during a connection
Section titled “Dropped during a connection”| Event | data |
Condition | What to do |
|---|---|---|---|
unsubscribed |
{"topic":"<topic>","reason":"quota exceeded"} (JSON) |
A topic that requires a permission went over quota, and the connection still has other topics | That topic stops. The others continue |
close |
quota exceeded (plain text) |
The last topic on the connection was removed for going over quota | Do not reconnect immediately. Wait until the quota recovers |
close |
token revoked (plain text) |
Any token on the account was revoked. About 5 seconds later, every authenticated connection for that account closes | Reconnect with a token that is still valid. The revoked token gets invalid token |
If a write times out (nothing can be written for about 30 seconds), the server drops the connection and does not send a close event. Treat a disconnect with no close as a network problem, and reconnect using retry: 3000 (3 seconds).
