Integration Guide

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

Getting Started

Set up your project, create a publishable API key, 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
  • A publishable API key (ws_pk_…), created by an org owner or admin under API keys in the console

Base URL

https://api.wiredstrings.app/v1

Quick Start

Replace YOUR_PUBLISHABLE_KEY with your ws_pk_… key and my-project with your project slug:

Your first API call
curl -H "Authorization: Bearer YOUR_PUBLISHABLE_KEY" \
  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 send an API key as a Bearer token in the Authorization header.

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

Key Types

  • Publishable (ws_pk_…): reads translations and languages only. Safe to ship in web and mobile apps — use it for delivery and the SDK.
  • Secret (ws_sk_…): the management API (CLI push and export, CI, the Figma plugin). Keep it on servers, in CI secrets and on developer machines.

Scopes and Projects

Every key lists the projects it may reach, or is explicitly granted all projects. A key for "project-a" cannot read translations from "project-b". Secret keys also hold the permission scopes chosen at creation: the console offers the presets CLI sync (pull/push), Content automation (CLI sync plus creating and editing keys and translations) and Custom.

Only organization owners and admins create keys, and a key works only while its creator is still an active owner or admin of the organization.

Token Introspection

Check a key's kind, scopes and the projects it can access:

GET /v1/token-info
curl -H "Authorization: Bearer YOUR_PUBLISHABLE_KEY" \
  https://api.wiredstrings.app/v1/token-info
Example response
{
  "token_id": "tok_abc123",
  "token_name": "Production delivery",
  "kind": "publishable",
  "scopes": ["translation:read", "language:read"],
  "all_projects": false,
  "projects": [
    { "id": "proj_1", "name": "My Project", "default_locale": "en" }
  ],
  "figma_plugin_enabled": false
}

Publishable keys (ws_pk_…) can only read translations, so they may ship in client apps. Never expose a secret key (ws_sk_…) in client-side code.

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_PUBLISHABLE_KEY" \
  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_PUBLISHABLE_KEY" \
  https://api.wiredstrings.app/v1/translations/my-project/en
# Response includes: ETag: "abc123"

# Subsequent request — send If-None-Match
curl -H "Authorization: Bearer YOUR_PUBLISHABLE_KEY" \
  -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_PUBLISHABLE_KEY" \
  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_PUBLISHABLE_KEY" \
  "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_PUBLISHABLE_KEY" \
  "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_PUBLISHABLE_KEY" \
  "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_PUBLISHABLE_KEY" \
  "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.

Use a publishable key (ws_pk_…) in client-side code — it can only read translations. Never expose a secret key (ws_sk_…) in the browser.

// npm install @wiredstrings/sdk

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

function App() {
  return (
    <WiredStringsProvider
      projectId="proj_abc123"
      {/* Publishable key (ws_pk_…) — never ship a secret key (ws_sk_…) client-side */}
      apiToken={import.meta.env.VITE_WS_PUBLISHABLE_KEY}
      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_SECRET_KEY" \
  "https://api.wiredstrings.app/v1/projects/my-project/export?locale=en&format=json" \
  -o en.json

# Export as CSV
curl -H "Authorization: Bearer YOUR_SECRET_KEY" \
  "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:
          # Secret key (ws_sk_…) with the "CLI sync" preset
          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. A publishable key (ws_pk_…) is enough for downloads; the CLI's push needs a secret key (ws_sk_…).