Skip to Content
Getting Started

Getting Started

Learn how to interact with the oJo API.

What you get

  • Synchronous by default. A render request returns the finished file in the same HTTP response — { id, imageUrl, createdAt } for an image, { id, pdfUrl, pages, createdAt } for a PDF. No job id to poll, no webhook to receive, no second request.
  • Bulk in one call. POST /v1/image/bulk renders many images from many templates in a single request, at 1 credit per image.
  • 60 renders per minute. Image and PDF endpoints share 60 requests per 60 seconds; template endpoints allow 30. See Rate Limits.
  • Your files do not expire. Rendered images and PDFs stay at their URL for as long as your account is active. Nothing removes them on a timer — a file is deleted when you delete it.
  • 30 credits to start, no card. Signing up issues 30 trial credits, valid for 30 days. No payment method is required to get them.
  • Renders in about a second. Measured against production on 2026-08-11: a saved template renders in ~0.6 s at the median, self-contained HTML in ~0.85 s, and HTML that pulls external fonts or images in ~1.2 s — p95 under 1.8 s in all three cases. A measurement, not an SLA.

Base URL

https://api.ojo.so

The API is described in OpenAPI 3.1 at https://api.ojo.so/openapi.json . It needs no API key.

Authentication

To authenticate with the API, you need to provide an API key in the Authorization header.

Authorization: Bearer <your API key>

You can obtain your API key from the API settings .

Response Codes

The API uses standard HTTP response codes to indicate the success or failure of an API request.

  • 200 OK - Everything worked as expected.
  • 202 Accepted - Sent with Prefer: respond-async: the render is queued and its status is at the Location address. See Async renders.
  • 400 Bad Request - The body is not valid JSON, a bulk request asks for more images than the rate limit allows, or the Idempotency-Key is not valid (code is idempotency_key_invalid). See Safe retries.
  • 401 Unauthorized - No valid API key provided.
  • 402 Payment Required - Not enough credits for the request. The message links to the plans.
  • 403 Forbidden - The account is suspended, or the API key may not use this endpoint. A key created for a connected AI app cannot delete.
  • 404 Not Found - The requested resource doesn’t exist, or is not yours.
  • 405 Method Not Allowed - The path exists but does not take this method. The Allow header lists the methods it takes.
  • 409 Conflict - A request with the same Idempotency-Key is still rendering. code is idempotency_key_in_progress, and retryAfter gives the wait in seconds.
  • 410 Gone - The image transform endpoint was removed. Render images with POST /v1/image/html or POST /v1/image/template.
  • 413 Payload Too Large - The request body is larger than 1 MB.
  • 415 Unsupported Media Type - The body is not of the type the endpoint reads. logs names the one it wants: text/html for the HTML endpoints, application/json for the JSON ones.
  • 422 Unprocessable Entity - The request cannot be served as sent, or the render failed. logs says why. code is idempotency_key_reused when the Idempotency-Key was sent before with a different request.
  • 429 Too Many Requests - Over the rate limit, or another multi-page PDF from your account is still rendering. code names which, and retryAfter gives the wait in seconds. See Rate Limits.
  • 500 Internal Server Error - We had a problem with our server. Try again later.
  • 503 Service Unavailable - A service the request needs is unavailable. code names it: queue_full when 100 renders are already waiting, queue_unavailable when the render queue cannot be reached, render_unavailable when the render failed on our side, database_unavailable when oJo could not reach its database, and auth_unavailable when it could not check your API key. Retry-After and retryAfter give the wait in seconds, as in a 429.
  • 504 Gateway Timeout - The render was not done within 90 seconds. code is render_timeout, and nothing is charged.

Rate Limits

The oJo API implements endpoint-specific rate limiting to ensure fair usage and system stability. Rate limits are tracked per user and enforced across all API requests.

Current Rate Limits

Endpoint TypeRate LimitTime WindowBlock Durationcode in the 429
Image and PDF Operations60 requests60 seconds120 secondsrate_limited.render
Template Operations30 requests60 seconds120 secondsrate_limited.template
PDF Preview (editor)30 requests1 hournonerate_limited.pdf_preview
Credits30 requests60 secondsnonerate_limited.credits

One multi-page PDF (pages) renders per account at a time. Another one sent meanwhile gets a 429 with code: "pdf_in_progress" until the first finishes.

When you make an API request, the response will include headers that provide information about your current rate limit status:

X-RateLimit-Policy: <endpoint-type>:<requests>/<seconds>s X-RateLimit-Limit: <maximum requests allowed> X-RateLimit-Remaining: <requests remaining> X-RateLimit-Reset: <ISO timestamp when the limit resets>

Rate Limit Exceeded

If you exceed the rate limit, you’ll receive a 429 Too Many Requests response with the following headers:

HTTP/1.1 429 Too Many Requests X-RateLimit-Policy: <endpoint-type>:<requests>/<seconds>s X-RateLimit-Limit: <maximum requests allowed> X-RateLimit-Remaining: 0 X-RateLimit-Reset: <ISO timestamp when the limit resets> Retry-After: <seconds until next request is allowed>

The body names the cause, the limit and the wait. retryAfter equals Retry-After:

{ "error": true, "code": "rate_limited.render", "message": "Render rate limit reached: 60 image and PDF renders per minute", "logs": ["Try again in 2 minutes."], "retryAfter": 87 }

Best Practices

To work effectively with these rate limits:

  1. Monitor the X-RateLimit-Remaining header to track your remaining requests
  2. Use the X-RateLimit-Reset timestamp to schedule requests appropriately
  3. When receiving a 429 response, wait for the duration specified in the Retry-After header before retrying
  4. Implement exponential backoff in your retry logic
  5. Consider implementing request queuing for high-volume operations

Example Response Headers

For image and PDF operations:

X-RateLimit-Policy: image:60/60s X-RateLimit-Limit: 60 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 2024-11-06T15:30:45.066Z Retry-After: 87

For template operations:

X-RateLimit-Policy: template:30/60s X-RateLimit-Limit: 30 X-RateLimit-Remaining: 29 X-RateLimit-Reset: 2024-11-06T15:30:45.066Z

Resource Limits

Your HTML runs in a browser, JavaScript included. Browser storage (localStorage, sessionStorage, IndexedDB, the Cache API) and Web Workers are off, so a library that needs them will not work.

Images, fonts, stylesheets and scripts load from public https:// addresses. Images can also be data: URLs. Plain http:// and private or internal addresses are refused.

LimitValue
Each document, script, stylesheet or font1 MB
Each image5 MB
All resources of one page together10 MB
Requests per page100
Requests a page may attempt, refused, cached and data: ones included500, past it the render fails with a 422
URLs of those requests together, data: URLs included16 MB, past it the render fails with a 422
Requests in flight at once, per page6 (more wait their turn)
Port443 only
Time to fetch one resource5 s
Redirects per resource5
Page load5 s
Wait for render-complete (charts)5 s
Rendering one image30 s
Rendering one PDF60 s
Rendered HTML of an image template5 MB

A resource that breaks a limit or is refused fails the render. When render-complete does not appear within 5 s, the page is captured as it is. A render past its time limit gets a 422 that names the limit, and nothing is charged.

Caveats

oJo API is currently in public beta and subject to change. We will do our best to notify you of any changes.