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

# Temporary links

> Give a request without a browser session short-lived access to one route of your app.

A temporary link opens one route of your app, for a few minutes, to a request
that has no Marina sign-in. Use it when something other than a signed-in
browser must fetch or post to your app: for example, iOS downloading an
installation manifest and app archive, or a device posting a profile service
response. Everything else in the app stays behind sign-in.

## Enable links

Declare the service in `marina.json`. It requires an `entrypoint`:

```json marina.json theme={null}
{
  "schema": 1,
  "entrypoint": "src/app.ts",
  "runtime": { "grants": "v1" }
}
```

Adding the service to an existing app may require a publisher to approve the
deployment before it becomes live.

## Create a link

Create links from your handler, usually while serving a signed-in request:

```ts theme={null}
const link = await marina.grants.create({
  path: "/api/builds/42/manifest.plist",
  methods: ["GET"],
  expiresIn: 900,
});

link.url;       // https://<app URL>/api/builds/42/manifest.plist?marina_grant=…
link.token;     // the value of marina_grant alone
link.expiresAt; // ISO 8601 time the link stops working
```

| Input | Rules |
| - | - |
| `path` | Required. The exact path the link opens, starting with a single `/`, at most 1,024 bytes. See below. |
| `methods` | One or more of `GET`, `POST`, `PUT`. Default `["GET"]`. A `GET` link also answers `HEAD`. |
| `expiresIn` | Seconds until the link stops working, 1 to 900. Default 900 (15 minutes). |
| `maxBodyBytes` | Largest `POST` or `PUT` body the link accepts, 0 to 10 MiB. Default 64 KiB. |

The path must be in normalized URL form: percent-encode non-ASCII characters
and do not encode `.` characters. It cannot contain a query string, fragment,
whitespace, backslash, or `.` or `..` segments, and it cannot be under
`/__marina/` or `/.marina/`. An invalid input fails with `INVALID_INPUT`.

`link.url` uses the app's Marina URL. A link created by the live version works
on any of the app's URLs, including a custom domain: append
`?marina_grant=<token>` to the same path. A link created by a version that is
not live, such as a proposed version awaiting approval, opens only that
version.

## How a link request is served

| Request | Response |
| - | - |
| Granted path and method, before expiry | Your app's response, unchanged, including status (such as `206`), `Content-Type`, `Content-Length`, and `Content-Range`. The link can be used any number of times until it expires, so `Range` requests and resumed downloads work. |
| A different path, or query parameters other than `marina_grant` | `403` |
| An expired, modified, or unknown link, or a link for another app | `403` |
| A method the link does not grant | `405` with an `Allow` header |
| A body larger than `maxBodyBytes` | `413` |
| A path under `/__marina/` or `/.marina/` | `404` |
| Marina cannot check the link | `503`. Retry the request. |

Link requests never redirect to sign-in and never set a Marina cookie.
Responses carry `Cache-Control: private, no-store` and
`Referrer-Policy: no-referrer`, because the URL contains the link's token.

## What your app receives

| Header | Value |
| - | - |
| `x-platform-principal` | `grant` |
| `x-platform-grant-id` | The link's id |
| `x-platform-workspace-id` | The workspace |

Marina removes `marina_grant` from the URL and removes the `Cookie` and
`Authorization` headers. `x-platform-user-id` is not set, so a link request
never carries the identity of whoever opened it. If your app needs to know
which user or record a link is for, store that in your own data when you
create the link, for example keyed by the path.

## Examples

Install an iOS build with `itms-services`:

```ts theme={null}
// The Install button, served to a signed-in user: link to the manifest.
const manifest = await marina.grants.create({ path: `/api/builds/${id}/manifest.plist` });
return Response.redirect(
  `itms-services://?action=download-manifest&url=${encodeURIComponent(manifest.url)}`,
);

// The manifest route, served through the link: link to the archive for
// the manifest's software-package URL.
const archive = await marina.grants.create({ path: `/api/builds/${id}/app.ipa` });
```

Receive an Apple profile service response:

```ts theme={null}
const callback = await marina.grants.create({
  path: `/api/devices/enroll/${requestId}`,
  methods: ["POST"],
  maxBodyBytes: 64 * 1024,
});
// Use callback.url as the profile service payload URL.
```

Your app still validates everything a link delivers, such as signatures,
challenges, replayed requests, and device eligibility.

## Expiry and security

A link cannot be revoked on its own. It stops working when it expires (at most
15 minutes after creation) or within 10 seconds of the app being archived.
Anyone who has the URL can use it until then, so create links only for the
path and methods the caller needs, with the shortest practical expiry.

The token is removed before the request reaches your app, and Marina's
request logs for the app record only the path.

## Local development

When `marina.json` declares `runtime.grants`, `marina.grants.create` works
under [`marina dev`](/guides/local-development). Links point at the local URL,
and link requests reach your app with the same headers as in production. The
local server does not enforce a link's path, methods, or expiry, so test those
against a deployed app.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.