Skip to main content
Every route in the API reference is a method on one of the client’s accessors (pages(), blogPosts(), testimonials() …), and every answer is a typed model. How the calls themselves work (arguments, models, ids, scopes, paging) is the same as in plain TypeScript: TypeScript → Calling the API. This page shows where they go in AdonisJS.

Read in a controller

The controller

Terminal
app/controllers/blog_posts_controller.ts
findBlog() (and every find…()) answers null for a 404 instead of throwing.

The routes

start/routes.ts
#generated/controllers is regenerated by node ace serve and node ace test; run node ace codegen before a bare tsc.

Write, after your own validation

Validate what your visitors send with VineJS first, so a bad request never reaches the site. This one saves a visitor’s testimonial as a draft for an editor to review and publish in the CMS:
Route it beside the blog’s:
start/routes.ts
A draft write needs the type’s write scope, here content:testimonials:write. Without aposMode: 'draft' the create also takes it live, which needs content:testimonials:publish too (What an API key reaches).

Upload a visitor’s file

The site reads a file’s type from its name, so keep the extension:
app/controllers/attachments_controller.ts
start/routes.ts
An upload needs a media type’s write scope (content:images:write here). A key with a signing secret signs it by its SHA-256, as every write (Signing requests).

Map SDK errors to responses

Anything the SDK throws is an AttractError, and each carries the site’s own HTTP status. Left alone, AdonisJS’s exception handler answers your caller with that status, so a key the site refuses (401) would look like your caller isn’t signed in. Map them once:
app/exceptions/handler.ts

Reported, never leaked

report() logs each one with its message (the site, route and status), never a key. Your caller sees only your own words.

Retries already done

The SDK retried what was safe before it threw: a RateLimited here has already waited. What is retried.
ValidationFailed.errors maps each field path to the site’s messages ({ quote: ['Required.'] }): pass them on, as above, when your caller sent those fields.

Long lists and slow calls

One page per request

A controller reads one page (listBlogs({ page })); …All() walks every page, best in a job; …Pick() walks it for only the fields you name (Find an id).

AI routes set their own timeout

Run them from a queue job, not a request: see Jobs, cache and health.

Next: testing

Japa tests with fakeAttract().