Integration Guide

Everything you need to fetch, format, and ship translations from WiredStrings into your app.

Getting Started

Set up your project, create an API token, and make your first request.

Prerequisites

  • A WiredStrings account with at least one project
  • At least one translation key with values in your target language
  • An API token with translations:read scope

Base URL

https://api.wiredstrings.app/v1

Quick Start

Replace YOUR_API_TOKEN with your token and my-project with your project slug:

Your first API call
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://api.wiredstrings.app/v1/translations/my-project/en
Example response
{
  "project": "my-project",
  "language": "en",
  "format": "flat",
  "translations": {
    "auth.login.title": "Sign In",
    "auth.login.button": "Log In",
    "dashboard.welcome": "Welcome back, {{name}}"
  }
}

Authentication

All API requests require a Bearer token in the Authorization header.

curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://api.wiredstrings.app/v1/translations/my-project/en

Token Scopes

Tokens are scoped to specific projects. A token with access to "project-a" cannot read translations from "project-b".

Token Introspection

Check which projects a token can access:

GET /v1/token-info
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://api.wiredstrings.app/v1/token-info
Example response
{
  "token": {
    "id": "tok_abc123",
    "name": "Production Token",
    "scopes": ["translations:read"],
    "expires_at": "2027-01-01T00:00:00Z"
  },
  "projects": [
    { "id": "proj_1", "name": "My Project" }
  ]
}

Never expose API tokens in client-side code. Use a backend proxy or download translations at build time.

Fetch All Translations

GET /v1/translations/{projectId}/{localeCode}

Parameters

Name Type Required Description
projectIdstringYesYour project ID (find it in the project Settings tab)
localeCodestringYesLanguage code (e.g., en, de, fr-FR)
formatstringNoOutput format: flat (default), nested, android_xml, ios_strings, ios_xcstrings
tagsstringNoComma-separated tag filter (AND logic)
include_tagsbooleanNoInclude tag metadata in response
resolve_fallbacksbooleanNoResolve missing keys from fallback languages (default: true)
renderbooleanNoRender interpolation variables server-side

Basic Request

curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://api.wiredstrings.app/v1/translations/my-project/en

ETag Caching

Every response includes an ETag header. Send it back with If-None-Match to get a 304 Not Modified when translations haven't changed.

ETag caching flow
# First request — save the ETag
curl -i -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://api.wiredstrings.app/v1/translations/my-project/en
# Response includes: ETag: "abc123"

# Subsequent request — send If-None-Match
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H 'If-None-Match: "abc123"' \
  https://api.wiredstrings.app/v1/translations/my-project/en
# Returns 304 Not Modified if translations haven't changed
Example response (flat format)
{
  "project": "my-project",
  "language": "en",
  "format": "flat",
  "translations": {
    "auth.login.title": "Sign In",
    "auth.login.button": "Log In",
    "auth.login.forgot": "Forgot password?",
    "dashboard.welcome": "Welcome back, {{name}}"
  }
}

Fetch Single Key

GET /v1/translations/{projectId}/{localeCode}/{key}
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://api.wiredstrings.app/v1/translations/my-project/en/auth.login.title
Example response
{
  "project": "my-project",
  "language": "en",
  "key": "auth.login.title",
  "value": "Sign In"
}

Server-Side Interpolation

Use {{variable}} syntax in your translation values. WiredStrings can render them server-side when you pass render=true.

Template Value

"dashboard.welcome": "Welcome back, {{name}}! You have {{count}} notifications."

Rendered Request

curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://api.wiredstrings.app/v1/translations/my-project/en?render=true&name=John&count=5"
Rendered response
{
  "translations": {
    "dashboard.welcome": "Welcome back, John! You have 5 notifications."
  }
}

Reserved parameters (format, tags, render, etc.) are not available as variable names.

Responses with render=true are not cached by ETag since the output depends on query parameters.

Output Formats

Control the response shape with the format query parameter. Default is flat. CSV is available via the Export endpoint.

?format=flat
{
  "translations": {
    "auth.login.title": "Sign In",
    "auth.login.button": "Log In",
    "auth.login.forgot": "Forgot password?"
  }
}

Working with Tags

Organize translation keys with tags and fetch only the subset you need.

Filter by Tags

curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://api.wiredstrings.app/v1/translations/my-project/en?tags=onboarding,mobile"

Multiple tags use AND logic — only keys that have all specified tags are returned.

Include Tag Metadata

Set include_tags=true to get tags alongside each value:

curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://api.wiredstrings.app/v1/translations/my-project/en?include_tags=true"
Enriched response
{
  "translations": {
    "onboarding.welcome": {
      "value": "Welcome!",
      "tags": ["onboarding", "mobile"]
    },
    "onboarding.skip": {
      "value": "Skip for now",
      "tags": ["onboarding"]
    }
  }
}

Fallback Languages

When a translation is missing in the requested language, WiredStrings resolves it from fallback languages automatically.

How It Works

Each language can define a fallback_code. When a key is missing:

  1. Check the requested locale (e.g. en-GB)
  2. Check the parent locale if set (e.g. en)
  3. Check the project's default language

Maximum fallback depth is 5 to prevent circular references.

Disable Fallbacks

Return only keys that exist in the requested locale:

curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://api.wiredstrings.app/v1/translations/my-project/en-GB?resolve_fallbacks=false"

Backend Integration

Full client examples with ETag caching for Go, Node.js, and Python.

Always use ETag caching with If-None-Match to avoid re-downloading unchanged translations.

package i18n

import (
	"encoding/json"
	"fmt"
	"net/http"
	"sync"
)

type Client struct {
	baseURL string
	token   string
	mu      sync.RWMutex
	cache   map[string]map[string]string // locale -> key -> value
	etags   map[string]string            // locale -> etag
}

func New(baseURL, token string) *Client {
	return &Client{
		baseURL: baseURL, token: token,
		cache: make(map[string]map[string]string),
		etags: make(map[string]string),
	}
}

func (c *Client) Fetch(project, locale string) (map[string]string, error) {
	url := fmt.Sprintf("%s/v1/translations/%s/%s", c.baseURL, project, locale)
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+c.token)

	c.mu.RLock()
	if etag, ok := c.etags[locale]; ok {
		req.Header.Set("If-None-Match", etag)
	}
	c.mu.RUnlock()

	resp, err := http.DefaultClient.Do(req)
	if err != nil { return nil, err }
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusNotModified {
		c.mu.RLock()
		defer c.mu.RUnlock()
		return c.cache[locale], nil
	}

	var result struct { Translations map[string]string `json:"translations"` }
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err }

	c.mu.Lock()
	c.cache[locale] = result.Translations
	c.etags[locale] = resp.Header.Get("ETag")
	c.mu.Unlock()

	return result.Translations, nil
}

Frontend Integration

Use the SDK for the fastest setup, or integrate manually with React, Next.js, or vanilla JavaScript.

Never expose API tokens in client-side code. Proxy requests through your backend or download translations at build time.

// npm install @wiredstrings/sdk

import { WiredStringsProvider, useTranslation, T } from '@wiredstrings/sdk/react';

function App() {
  return (
    <WiredStringsProvider
      projectId="proj_abc123"
      {/* Use a read-only public token — never expose write-scoped tokens client-side */}
      apiToken={import.meta.env.VITE_WS_TOKEN}
      defaultLocale="en-US"
    >
      <YourApp />
    </WiredStringsProvider>
  );
}

function Header() {
  const { t, locale, setLocale, isLoading } = useTranslation();
  if (isLoading) return <p>Loading…</p>;
  return (
    <header>
      <h1>{t('nav.home')}</h1>
      <p>{t('welcome.message', { name: 'Alice' })}</p>
      <button onClick={() => setLocale('de-DE')}>Deutsch</button>
    </header>
  );
}

// Or use the inline component:
// <T k="nav.home" />

Mobile Integration

Download translations in native formats for iOS and Android.

For production apps, prefer downloading translations in your CI/CD pipeline and bundling them with your app.

import Foundation

class WiredStringsClient {
    private let baseURL: String
    private let token: String

    init(baseURL: String, token: String) {
        self.baseURL = baseURL
        self.token = token
    }

    /// Download .xcstrings for a locale and save to app bundle
    func downloadStrings(project: String, locale: String) async throws -> URL {
        let url = URL(string: "\(baseURL)/v1/translations/\(project)/\(locale)?format=ios_xcstrings")!
        var request = URLRequest(url: url)
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

        let (data, _) = try await URLSession.shared.data(for: request)

        let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            .appendingPathComponent("\(locale).xcstrings")
        try data.write(to: fileURL)
        return fileURL
    }
}

// CI/CD alternative — download at build time:
// curl -H "Authorization: Bearer $TOKEN" \
//   "https://api.wiredstrings.app/v1/translations/my-app/en?format=ios_xcstrings" \
//   -o en.xcstrings

Export & Import

Bulk export translations in various formats for offline use or migration.

Using the CLI (recommended)

# One-time setup
wiredstrings init

# Export a single locale to a file
wiredstrings export --locale en-US --format flat_json > en-US.json
wiredstrings export --locale de-DE --format android_xml -o strings.xml

# Pull all configured locales at once
wiredstrings pull

# Push default locale back to WiredStrings
wiredstrings push

Requires .wiredstrings.yml — run wiredstrings init to create one.

Or via the API directly:

GET /v1/projects/{projectId}/export?locale={locale}&format={format}
# Export as JSON
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://api.wiredstrings.app/v1/projects/my-project/export?locale=en&format=json" \
  -o en.json

# Export as CSV
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://api.wiredstrings.app/v1/projects/my-project/export?locale=en&format=csv" \
  -o en.csv

CI/CD Integration

Automate translation downloads in your build pipeline.

download-translations.sh
#!/bin/bash
set -euo pipefail

BASE_URL="https://api.wiredstrings.app/v1"
PROJECT="my-project"
LOCALES=("en" "de" "fr" "es")
OUTPUT_DIR="./src/locales"

mkdir -p "$OUTPUT_DIR"

for locale in "${LOCALES[@]}"; do
  echo "Downloading $locale..."
  curl -sf -H "Authorization: Bearer $WIREDSTRINGS_TOKEN" \
    "$BASE_URL/translations/$PROJECT/$locale?format=nested" \
    -o "$OUTPUT_DIR/$locale.json"
done

echo "Done — downloaded ${#LOCALES[@]} locales"
# 1. One-time setup (run locally)
#    npm install -g @wiredstrings/cli
#    wiredstrings init     ← creates .wiredstrings.yml

# 2. Sync in CI
name: Sync Translations
on:
  schedule:
    - cron: '0 6 * * 1'
  workflow_dispatch:

jobs:
  sync:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - name: Pull translations
        env:
          WIREDSTRINGS_API_TOKEN: ${{ secrets.WIREDSTRINGS_API_TOKEN }}
        run: |
          npm install -g @wiredstrings/cli
          wiredstrings pull --json
      - uses: peter-evans/create-pull-request@v7
        with:
          commit-message: 'chore: update translations'
          title: 'Update translations from WiredStrings'
          branch: chore/update-translations

Downloading translations at build time means your app doesn't need runtime API access, and your API tokens stay out of client-side bundles.