Dynalinks
Using Dynalinks API

API basics

The Dynalinks REST API lets you create, update, list and delete links, shorten URLs, and manage the iOS and Android apps of a project from your own backend. This page covers the conventions shared by every endpoint. For the full endpoint reference, see API reference.


Base URL

All endpoints live under a single versioned base URL:

https://dynalinks.app/api/v1

Always use https. Your project's subdomain (for example yourproject.dynalinks.app) serves your links, not the API.


Authentication

Every request needs a project API key. You can find it in the console under Settings > API Keys. Send it in the Authorization header:

curl https://dynalinks.app/api/v1/links \
  --header 'Authorization: Token YOUR_API_KEY'

You can also pass it as an api_key query parameter. See API authorization for both options.

A key belongs to one project, so every request works on that project's data. You never pass a project ID.

Note

When you create a key with custom scopes, it can only call the endpoints you selected (for example links.index or links.create). A key with no scopes selected has full access. A request outside the key's scopes is rejected with 401.

Warning

Your API key gives write access to your project. Keep it on your server and never ship it inside a mobile or web app. The mobile SDKs use a separate client API key from Settings > Mobile SDK, which cannot be used with this API.


Requests and responses

  • Request and response bodies are JSON.
  • POST, PATCH and PUT requests must send Content-Type: application/json. Without it, the API responds with 400 Bad Request.
  • GET and DELETE requests take their parameters in the query string.
  • Field names use snake_case, for example ios_fallback_url.
  • Resources are identified by an id string (a UUID) that is returned when you create or list them.
curl --request POST https://dynalinks.app/api/v1/links \
  --header 'Authorization: Token YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"name": "Spring sale", "path": "sale/spring", "url": "https://example.com/sale"}'

Status codes and errors

Successful requests return:

StatusMeaning
200 OKThe request succeeded. The body contains the resource.
201 CreatedA resource was created. The body contains the new resource.
204 No ContentThe resource was deleted. The body is empty.

Failed requests return a JSON body with a single error message:

{
  "error": "Missing or invalid api_key"
}
StatusWhen it happens
400 Bad RequestThe body is not valid JSON, the Content-Type is not application/json, a required parameter is missing, or page / limit is not a positive number.
401 UnauthorizedThe API key is missing or invalid, the account is suspended, the key does not have the required scope, or your plan does not allow the action (for example, exceeding the free tier link limit). The error message tells you which.
404 Not FoundNo resource with that id exists in the project the key belongs to.
422 Unprocessable ContentThe data failed validation. The error message lists every problem in one sentence, for example Package name is invalid and Fingerprints can't be blank.
429 Too Many RequestsYou exceeded a rate limit. See Rate limits.
503 Service UnavailableThe request took too long to process. The response includes a Retry-After: 1 header. Retry after a short pause.

Note

The URL shortening endpoint (POST /api/v1/short_links) returns some of its errors under an errors key instead of error. Check for both if you handle errors generically.


Pagination

List endpoints (GET /api/v1/links, GET /api/v1/ios_apps, GET /api/v1/android_apps) return results one page at a time.

ParameterDefaultDescription
page1The page number, starting at 1.
limit20How many items to return per page.

Both must be positive whole numbers. 0, negative values, or text return 400 Bad Request.

curl 'https://dynalinks.app/api/v1/links?page=2&limit=50' \
  --header 'Authorization: Token YOUR_API_KEY'

The response wraps the items in a named array and adds a meta object:

{
  "links": [
    { "id": "1f0c6e9a-2b7d-4c1e-9a4f-6d2b8e3c5a71", "name": "Spring sale", "path": "sale/spring", "...": "..." }
  ],
  "meta": {
    "page": 2,
    "limit": 50,
    "count": 134,
    "last": 3,
    "from": 51,
    "to": 100,
    "previous": 1,
    "next": 3
  }
}
FieldDescription
pageThe current page.
limitItems per page.
countTotal number of items across all pages.
lastThe number of the last page.
from, toThe position of the first and last item on this page. Both are 0 when the page is empty.
previous, nextThe previous and next page numbers. next is left out on the last page.

The meta object may include additional fields (such as page URLs). Rely only on the ones listed above.

To fetch everything, keep requesting the next page until next is missing. Asking for a page past the end returns 200 with an empty array, not an error.

Note

Results have no guaranteed order unless you sort them. When paging through a list that may change while you read it, sort by created_at (see below) so that items do not shift between pages.


Filtering and sorting

List endpoints accept filters in a q parameter. Each filter is written as q[<field>_<condition>]=<value>.

Common conditions:

ConditionMeaningExample
eqequalsq[path_eq]=sale/spring
not_eqdoes not equalq[name_not_eq]=Test
contcontains (case insensitive)q[name_cont]=sale
startstarts withq[path_start]=products/
inis one ofq[path_in][]=a&q[path_in][]=b
gt, gteq, lt, lteqgreater / less than (or equal)q[created_at_gteq]=2026-01-01
null, presentis empty / is not emptyq[shortened_path_present]=1

Fields you can filter links by:

FieldDescription
nameThe link name.
pathThe link path, for example sale/spring.
shortened_pathThe short path, for links that were shortened.
clicksThe stored click count.
created_atWhen the link was created.
created_viaWhere the link was created: api or console.
ios_app_bundle_idThe bundle ID of the link's iOS app.
android_app_package_nameThe package name of the link's Android app.

The fields available for iOS and Android apps are listed in iOS and Android apps API.

To sort, pass q[s] with a field name and asc or desc:

curl --get https://dynalinks.app/api/v1/links \
  --header 'Authorization: Token YOUR_API_KEY' \
  --data-urlencode 'q[path_start]=products/' \
  --data-urlencode 'q[s]=created_at desc' \
  --data-urlencode 'limit=100'

Warning

Filters on fields that are not in the list above are ignored, not rejected. A typo in a field name returns the unfiltered list, so double-check filter names when a query returns more than you expect.

You do not need to filter by path before creating a link. Use the upsert endpoint to create or update a link in one request.


Delete a link with its id, the UUID returned when the link was created or listed. The link path does not work here.

curl --request DELETE https://dynalinks.app/api/v1/links/1f0c6e9a-2b7d-4c1e-9a4f-6d2b8e3c5a71 \
  --header 'Authorization: Token YOUR_API_KEY'

A successful delete returns 204 No Content with an empty body. An unknown id returns 404 Not Found.

If you only know the path, look the link up first and use the id from the result:

curl --get https://dynalinks.app/api/v1/links \
  --header 'Authorization: Token YOUR_API_KEY' \
  --data-urlencode 'q[path_eq]=sale/spring'

Important

Deleting a link is permanent. The URL stops resolving immediately, so anyone who opens it afterwards no longer reaches your app or fallback.


Rate limits

Write requests are rate limited per API key. Read requests and deletes of links are not rate limited.

EndpointsShared limit per API key
Link writes: POST /api/v1/links, POST /api/v1/links/upsert, PATCH / PUT /api/v1/links/:id600 requests per minute
URL shortening: POST /api/v1/short_links600 requests per minute
iOS app writes: POST, PATCH / PUT and DELETE on /api/v1/ios_apps600 requests per minute
Android app writes: POST, PATCH / PUT and DELETE on /api/v1/android_apps600 requests per minute

The endpoints in one row share a single budget. For example, 400 link creates and 200 link updates in the same minute use up the link writes limit for that key, while URL shortening still has its own 600. Each API key has its own budgets.

When you go over a limit, the API responds with:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json

{"error": "Rate limit exceeded. Too many requests."}

The response does not include a Retry-After header. Wait a few seconds and retry, ideally with exponential backoff. If you need to create a large number of links, spread the requests out over time instead of sending them in parallel bursts.


Full reference

Every endpoint, parameter and response field is documented in the API reference. Related guides:

On this page