> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jojapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Code

> Take over your API's Worker: write the request handling yourself with the jojapi SDK.

On the edge gateway every API runs as its own Worker, generated from the [Target](/studio/api-targeting) form. When the form is not enough, for example to transform the origin's response, call several upstreams per request, or compute usage from the response body in a way no source expresses, take over the code.

<Info>
  Custom code is available for APIs served by the edge gateway (the Target tab shows an **Edge gateway** badge).
</Info>

## Ejecting from the form

On the Target tab, **View code** shows the generated `index.mjs`; **Edit this code** turns it into your file. From then on:

* your code runs as the API's Worker; the targets form is kept for reference and no longer applies,
* [variables](/studio/api-targeting#variables) still reach the Worker as `env.NAME` (secrets included),
* every save deploys the new code within seconds,
* **Back to the form** regenerates the Worker from the targets and discards your code.

## The SDK

The file `jojapi.mjs` is uploaded next to your code. It handles the platform contract (authentication, metering, logging) so your code only deals with the request.

```js theme={null}
import { defineApi } from "./jojapi.mjs";

export default defineApi({
  async fetch(request, ctx, env) {
    const upstream = await fetch("https://api.example.com/v1" + ctx.path + "?" + ctx.rawQuery, {
      method: ctx.method,
      headers: ctx.forwardHeaders({ "x-api-key": env.UPSTREAM_API_KEY }),
      body: ctx.bodyText,
    });
    const data = await upstream.json();
    ctx.usage({ tokens: data.usage?.total_tokens ?? 0 });
    return Response.json({ result: data.result }, { status: upstream.status });
  },
});
```

`defineApi` accepts three keys, alone or combined:

| Key                        | Meaning                                                                                                        |
| -------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `fetch(request, ctx, env)` | your handler; return a `Response`                                                                              |
| `targets`                  | declarative targets in the form's format; used when no `fetch` is given (this is what the generated code does) |
| `usage`                    | usage sources per endpoint id, used with `targets`                                                             |

### `ctx`

| Field / method                                                                                              | Description                                                                                             |
| ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `ctx.method`, `ctx.path`, `ctx.query`, `ctx.rawQuery`, `ctx.headers`, `ctx.bodyText`, `ctx.url`, `ctx.host` | the consumer's request; `query` is a `URLSearchParams`, `bodyText` is already read for non-GET requests |
| `ctx.user`                                                                                                  | `{ id, username, legacyId }` of the consumer                                                            |
| `ctx.plan`                                                                                                  | `{ id, type }`: the plan slug and `periodic` or `payasyougo`                                            |
| `ctx.endpoint`                                                                                              | `{ id, method, path, credits }` of the matched endpoint                                                 |
| `ctx.subscription`                                                                                          | `{ id }`                                                                                                |
| `ctx.ip`, `ctx.country`                                                                                     | the consumer's IP address and country code                                                              |
| `ctx.forwardHeaders(extra)`                                                                                 | the consumer's headers without hop-by-hop ones, plus `extra`                                            |
| `ctx.proxy(targets, usageSources?)`                                                                         | forward to declarative targets exactly like the generated Worker and return the response                |
| `ctx.usage({ slug: units })`                                                                                | report the units of each billable object this request consumed                                          |
| `ctx.log(level, message, data?)`                                                                            | write to the API's request log (visible in Studio)                                                      |
| `ctx.error(status, message, extra?)`                                                                        | a gateway-formatted error response                                                                      |

`env` holds your variables (`env.UPSTREAM_API_KEY`) as strings.

### Reporting usage

Call `ctx.usage()` with the billable object **slug** as key. Rules of the platform still apply: on a fixed cost the reported amount can lower the bill but never exceed the declared amount; a 5xx response bills 0 on every object; values must be non-negative integers. An endpoint whose objects are not reported bills its fixed amounts, or 0 for metered objects.

Streaming responses (`text/event-stream`) pass through untouched; report usage in-band with a `data: {"x-jojapi-credits-used": n}` message as described in [Adjusting usage](/studio/adjusting-credit-usage).

## Limits and behaviour

* Up to 30 seconds of CPU time and 50 outgoing requests per invocation; an upstream call has at most 90 seconds.
* IP addresses, non-standard ports and proxies are reached through the platform's egress relay automatically; a proxy is chosen with the `x-jojapi-relay-proxy` header holding the proxy URL, which `ctx.proxy` sets from a target's `proxy` field.
* Exceptions in your handler answer the consumer with a gateway `500` and are written to the API's log.
* Returned `x-jojapi-*` headers are reserved and removed; the gateway adds the usage and quota headers itself.
