API & SDK Issues
Troubleshoot API authentication, rate limits, WebSocket/SSE setup, and SDK usage on the platform.
Every REST route reads your identity from one of three credentials: a Clerk session (the browser), a verified Clerk JWT, or an ivy_ API key. The route then checks that the job, project or file belongs to you, and applies a rate limit for its route class. The sections below follow that order.
The examples use https://www.ivybiosciences.com, the host the ivybloom CLI talks to by default. The full route reference is in Jobs, Rate limits and SSE streaming.
401 Unauthorized
A 401 means the server could not verify who you are, so it rejected the request before it ran any route logic.
Common causes:
Expired API key
- A key can carry an expiry date. Check the key's expiry on the API keys page, or run
ivybloom apikeys list. - You can't renew an expired key. Create a new one and update your application.
Missing Authorization header
- Send the key as a Bearer token:
Authorization: Bearer ivy_<keyId>_<secret>. The server also accepts it in anX-API-KeyorX-Ivy-Api-Keyheader. - Common mistakes:
- Missing the
Bearerprefix:Authorization: ivy_abc_123(wrong). - Quotes around the token:
Authorization: Bearer "ivy_abc_123"(wrong). - A key that doesn't start with
ivy_. Every API key starts withivy_, and anything else is read as a session token.
- Missing the
Chat streams from the CLI
- A chat stream needs an API key. If a request carries only a CLI client id, the server refuses it with
401 CLI_API_KEY_REQUIRED. Runivybloom auth login --api-key ivy_…and retry. See CLI API keys.
Debugging checklist:
- Print the exact
Authorizationheader your code sends. Redact the secret, but check the shape. - Confirm the key has the shape
ivy_<keyId>_<secret>. - Rule out your application by repeating the request with
curl:
403 Forbidden
A 403 means the server knows who you are, but you aren't allowed to perform this action.
Common causes:
Resource not yours
- Each job, project and file is scoped to its owner. Reading one that belongs to another user or organization returns
403(or404, so the route doesn't confirm that it exists). - A shared resource is readable only after its owner has shared it with you or published a share link.
Organization role restrictions
- In an organization, your role decides what you can change. If you can read a project but can't submit jobs to it, ask an org admin to check your role.
Plan limits
- Plans differ in credits, concurrent jobs and AI reasoning tier, not in API access. Every account can create an
ivy_API key, including the free Seed plan. Compare plans on Pricing.
429 Rate Limited
A 429 means you've sent more requests than the route's rate limit allows.
How limits are applied
- Each route belongs to a rate-limit class, and each class has its own per-minute budget. Authenticated callers are keyed on the user, and anonymous callers are keyed on the client IP. Per-endpoint figures are on Rate limits.
- The
429body carries the error codeRATE_LIMITEDand aretryAftervalue in seconds.
Reading rate limit headers
| Header | Meaning |
|---|---|
Retry-After | Seconds to wait before retrying. Sent on 429 responses. |
X-RateLimit-Limit | Maximum requests in the current window, on routes that report it. |
X-RateLimit-Remaining | Requests left before the limit, on routes that report it. |
X-RateLimit-Reset | When the window resets, on routes that report it. |
Only Retry-After is sent on every 429, so write your client against that header.
Backoff strategy
Back off exponentially when you receive a 429:
- Read the
Retry-Afterheader value (in seconds). - Wait that many seconds before retrying.
- If the retry also returns
429, double the wait time. - Cap the maximum wait at 60 seconds.
- After 5 consecutive
429responses, stop retrying and log the failure.
Do not:
- Retry immediately in a tight loop.
- Retry without reading
Retry-After. - Open parallel connections to get around the limit. Authenticated limits are keyed on your user, not on the connection.
WebSocket and SSE setup
Job progress comes from the Modal orchestrator. The browser receives it over an SSE proxy on this app, so it never connects to Modal directly.
SSE (Server-Sent Events) connection
- The route checks that you can access the job before it opens the stream.
- SSE is one-way (server to client). Send commands with ordinary HTTP requests.
- The server sends a heartbeat about every 15 seconds and closes the stream after about 5 minutes. Reconnect if the job is still running.
- The route doesn't replay missed events. After a reconnect, fetch
GET /api/v1/jobs/{jobId}to catch up on the current state.
Guest SSE
Chats started without an account run under a guest id, and their jobs stream from a separate route:
- The route needs no API key. The orchestrator checks that the
guest_idowns the job and closes the stream on a mismatch. - This route is for guest sessions only. A public share link doesn't open a job stream.
WebSocket
There is no public WebSocket endpoint. The job WebSocket is internal to the compute backend, and external clients can't connect to it — use the SSE route above instead. See WebSocket Protocol if you're migrating code that opened a job WebSocket directly.
SDK version compatibility
The @pleach/* packages are the pleach agent runtime, and they are pre-1.0 (0.x). The REST API doesn't need them: any HTTP client with an ivy_ key can call it, and the ivybloom CLI is the supported command-line client.
Common SDK issues
- Type errors after an update.
0.xreleases can change types between minor versions, so pin an exact version and read the package changelog before you upgrade. - Import path changes. The packages use subpath exports (for example
@pleach/core/types). If an import path stops resolving after an update, check theexportsfield in the package'spackage.json.
Verifying your SDK version
CORS errors
CORS errors occur when a browser page on another origin calls the platform API.
Symptom: the browser console shows Access to fetch at 'https://www.ivybiosciences.com/api/...' from origin 'https://your-app.com' has been blocked by CORS policy.
Hosted platform
- The API allows browser calls only from the platform's own origins (
https://www.ivybiosciences.comandhttps://accounts.ivybiosciences.com). - A custom frontend on another domain can't call the API from the browser. Call it from your backend instead, which also keeps your API key off the client.
Self-hosted instances
- The allowed origins are a fixed list in
src/middleware/helpers/corsHelpers.tsandsrc/middleware/handlers/routeHandlers.ts, not an environment variable. Add your frontend origin to both lists and redeploy. - Don't allow
*in production, because that lets any website call the API with your users' cookies.
Preflight requests
- Browsers send an
OPTIONSpreflight before some API calls (POST, PUT, DELETE, or any call with custom headers). - If the preflight returns a non-2xx status or no CORS headers, the browser blocks the real request.
- When debugging, inspect the
OPTIONSrequest in the Network tab, not just the main request.
Timeout errors
Client-side timeouts
- Job submission returns as soon as the job is queued. The job then runs asynchronously, so the submit request doesn't need a long timeout.
- When polling, use a short per-request timeout (about 10 seconds) and poll every 5-10 seconds instead of holding one long request open.
Polling pattern for long-running jobs
Don't wait synchronously for a job to finish. Poll it instead:
- Submit the job with
POST /api/v1/jobs. The response carries the job id. - Poll
GET /api/v1/jobs/{jobId}for its status. - Stop when the status is terminal:
COMPLETED(orSUCCESSon some tools),FAILURE, orCANCELLED. - Fetch results with
ivybloom jobs results JOB_ID, or through the routes listed in Artifacts & storage.
You can also stream status over SSE (above) instead of polling.
Common request patterns
Submitting a job
The CLI equivalent is ivybloom run esmfold protein_sequence=MKTAYIAKQRQISFVKSHFSRQ.
Checking job status
Ticket checklist
If you need to contact support about an API issue:
- The endpoint URL and HTTP method
- The response status code and body (redact your API key)
- Any
Retry-AfterorX-RateLimit-*headers on the response - Your CLI version (
ivybloom version) or SDK version (npm list @pleach/core) - Whether the issue reproduces with
curl(this rules out your SDK or HTTP library) - Timestamps and your organization slug
Was this page helpful?