Browse documentation

Authentication

Every request to the Movik API carries a short-lived Cognito token. You mint it from your service credentials and reuse it until it expires.

How it works

Movik authenticates users with Amazon Cognito. Requests to the API are authorized by API Gateway against a Cognito-issued JWT, so the only thing the API accepts is a valid token — there is no API key, no HMAC signature and no IP allowlist.

Your service credentials are exchanged for that token by POST /api/integrations/token on movik.us. The exchange runs Cognito’s Secure Remote Password protocol, which means your password is verified without being transmitted to Cognito itself.

Two hosts are involved, and mixing them up is the most common setup mistake: mint tokens at movik.us, call endpoints at api.dev.movik.us.

Minting a token

bash
curl -X POST https://movik.us/api/integrations/token \
  -H 'Content-Type: application/json' \
  -d '{"email": "you@example.com", "password": "your-password"}'

On success you get 200 with the token, the absolute expiry as a millisecond timestamp, and the seconds remaining:

json
{
  "token_type": "Bearer",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_at": 1755640800000,
  "expires_in": 3600,
  "partner": { "id": "your-partner-id", "name": "Your Company" }
}

No refresh token is issued. That is deliberate: when access needs to be withdrawn, Movik disables the service user and the next mint fails, so residual access is bounded by one token lifetime rather than by however long a refresh token happens to live.

Using the token

Send it as a bearer token on every API request. Endpoints marked No token required in the reference accept the header too and ignore it, so a single client that always attaches it is fine.

bash
curl https://api.dev.movik.us/loads \
  -H "Authorization: Bearer $ID_TOKEN"

The one exception is the S3 upload step in Files: pre-signed URLs carry their own signature, and attaching a bearer token makes S3 reject the upload.

Token lifetime and caching

Tokens are short-lived, typically one hour. Cache the token in memory, reuse it across requests, and re-mint shortly before expires_at. Minting a token per request will trip the rate limit and is the single most common integration mistake.

Treat the token as a credential: never log it, never put it in a URL query string, never persist it to disk or a shared cache. Store the email and password in your secret manager, not in source.

A reference client

This handles caching, early refresh and the revoked-token case. Copy it rather than writing your own loop.

javascript
// movik.js — a token-caching client. No dependencies.
const TOKEN_URL = 'https://movik.us/api/integrations/token'
const API_URL = 'https://api.dev.movik.us'

let cached = { token: null, expiresAt: 0 }

async function getToken() {
  // Re-mint a minute early so a request never leaves with a token that
  // expires in flight.
  if (cached.token && Date.now() < cached.expiresAt - 60_000) {
    return cached.token
  }

  const res = await fetch(TOKEN_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email: process.env.MOVIK_EMAIL,
      password: process.env.MOVIK_PASSWORD,
    }),
  })

  if (!res.ok) {
    const body = await res.text()
    throw new Error(`Movik token request failed: ${res.status} ${body}`)
  }

  const data = await res.json()
  cached = { token: data.id_token, expiresAt: data.expires_at ?? 0 }
  return cached.token
}

export async function movik(path, options = {}) {
  const token = await getToken()

  const res = await fetch(`${API_URL}${path}`, {
    ...options,
    headers: {
      ...options.headers,
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
  })

  // A 401 on a token we believed was valid means it was revoked or the user
  // was disabled. Drop the cache so the next call re-mints, then surface it.
  if (res.status === 401) {
    cached = { token: null, expiresAt: 0 }
    throw new Error('Movik rejected the token (401). Credentials may be revoked.')
  }

  return res
}
python
# movik.py — the same contract, in Python.
import os, time, requests

TOKEN_URL = "https://movik.us/api/integrations/token"
API_URL = "https://api.dev.movik.us"

_cache = {"token": None, "expires_at": 0}

def _get_token():
    if _cache["token"] and time.time() * 1000 < _cache["expires_at"] - 60_000:
        return _cache["token"]

    res = requests.post(TOKEN_URL, json={
        "email": os.environ["MOVIK_EMAIL"],
        "password": os.environ["MOVIK_PASSWORD"],
    }, timeout=30)
    res.raise_for_status()

    data = res.json()
    _cache.update(token=data["id_token"], expires_at=data.get("expires_at") or 0)
    return _cache["token"]

def movik(method, path, **kwargs):
    res = requests.request(
        method, f"{API_URL}{path}",
        headers={"Authorization": f"Bearer {_get_token()}"},
        timeout=30, **kwargs,
    )
    if res.status_code == 401:
        _cache.update(token=None, expires_at=0)
    return res

Token endpoint errors

StatusMeaningWhat to do
400 invalid_requestThe body was not JSON, or email or password was missing.Fix the request shape. Retrying unchanged will not help.
401 invalid_grantThe password was wrong, or the Cognito user is disabled or unconfirmed. Movik returns one message for all three deliberately.Check the credentials. If they were working and stopped, assume access was revoked and contact your Movik representative.
403 invalid_clientThe email is not registered for API access, or its registration was deactivated. A valid Movik customer password that is not a registered integration gets this too.Contact your Movik representative. Do not retry.
429 rate_limitedToo many token requests from your address or for your account.Honour the Retry-After header. If you see this in normal operation you are minting per request rather than caching.

Losing access

Movik can withdraw access at any time by disabling your service user. When that happens token minting returns 401 and any token already issued stops being accepted once it expires. If your integration starts failing with 401 on credentials that were working, treat it as revoked rather than retrying, and get in touch.