Examples using curl to access API v2?

I’m trying to convert from API v1 to API v2 but cannot understand how to do that from the documentation. (The CLI and language interfaces to the API don’t appear to support all of the functionality I need, which is why I want to access the API directly.)

How do I actually use API v2? I’m looking for examples using curl from a shell script. As an example, how do I:

  1. Upload a file to storage using Add Storage.
  2. Wait for that to complete.
  3. Define where to add the file using the Add File endpoint.
  4. Wait for that to complete.

If I had one example like that using curl, I think I could figure out how to do all the other things I need to do with the API.

Hello @Tunnelblick!

Transitioning from API v1 to API v2 involves a few changes. Firstly, you’ll need to generate a personal access token by navigating to Crowdin Account Settings → API & SSO → New Token. This replaces the API key used in API v1. Your requests should include the following header: Authorization: Bearer .

API v2 also introduces the use of Storage. All files for localization, screenshots, Glossaries, and Translation Memories should first be uploaded to the Storage. You can do this using the Add Storage endpoint: API v2 Reference .

Once the file is in Storage, you can define where to add the file using the Add File endpoint: API v2 Reference .

Here’s a basic example using curl:

  1. Upload a file to storage:
curl -X POST https://api.crowdin.com/api/v2/storage \
-H 'Authorization: Bearer ' \
-F 'file=@/path/to/your/file'
  1. Add the file to your project:
curl -X POST https://api.crowdin.com/api/v2/projects//files \
-H 'Authorization: Bearer ' \
-d 'storageId=&name='

Please replace , `/path/to/your/file`, , , and with your actual values.

I hope this helps! If you have any further questions, feel free to ask.

Thank you, DianaO! I’ll get started.

Thanks for pointing me in the right direction, @DianaO.

After making some changes to your models, I got a macOS .strings source file uploaded.

However, Crowdin doesn’t show it on the “Sources” tab as having the correct number of strings:

(In the screenshot below, the first line is for a file uploaded using the v1 API and the second line is for the same file uploaded using the v2 API using the script below. After uploading I downloaded both files from Crown.com and they are identical, so the files should have the same number of strings.)

The number of strings in the file uploaded using the v2 API has many fewer strings than the file uploaded using the v1 API. The number of words (shown by hovering over the number of strings) is also much lower. When using …View Strings, many of the strings are dimmed.

Below is the code I used for uploading, after making the following changes from @DianaO’s models:

  • The endpoint is “storages”, not “storage”.
  • I added my personal access token after "Bearer ".
  • I used --data-binary instead of -F because-F prepends the file with several lines of metadata (the name of the file, etc.).
  • The Content-Type and Crowdin-API-FileName headers are required by the Add Storage API.
  • NOTE: I have also tried Content-Types of text/plain and text/strings but they have the same problem.
PROJECT_ID= <integer code extracted from the output of Crowdin's List Projects API>
CROWDIN_PERSONAL_ACCESS_TOKEN=<personal access token as described by @DianaO>

URL_ENCODED_FILE_NAME ="$( perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$FILE_NAME") "

curl  -X POST "https://api.crowdin.com/api/v2/storages" \
      -H "Authorization: Bearer $CROWDIN_PERSONAL_ACCESS_TOKEN" \
      -H 'Content-Type: application/octet-stream' \
      -H "Crowdin-API-FileName: $URL_ENCODED_FILE_NAME" \
      --data-binary "@$PATH_TO_FILE_TO_UPLOAD" \
ID ="$( grep --text \
             --extended-regexp \
             --only-matching \
             --max-count=1 \
             '"id":[^,]*,' "$PATH_TO_A_TEMPORARY_FILE" )"
if [ -z "$ID" ] ; then
      echo "ERROR: Cannot find storage Id!"
      echo "Response was '$( cat "$PATH_TO_A_TEMPORARY_FILE" )'"
      exit 1
ID="${ID:5}".         # remove "id:" at start
STORAGE_ID ="${ID%?}" # remove "," at end

Here’s the code I used to add the file to the source files:

           \"storageId\": $STORAGE_ID,
           \"name\":      \"$FILE_NAME\"
           \"type\":      \"macosx\"

curl -X POST "https://api.crowdin.com/api/v2/projects/$PROJECT_ID/files" \
     -H 'Content-Type: application/json' \
     -H "Authorization: Bearer $CROWDIN_PERSONAL_ACCESS_TOKEN" \
     --data-binary "$PAYLOAD" \

where STORAGE_ID, FILE_NAME, PROJECT_ID, and CROWDIN_PERSONAL_ACCESS_TOKEN are the same variables set in the above code for uploading.

Note that almost all error detection and handling has been removed from the above code for simplicity.

Thank you for the details! Do you use duplicate options in your project? Probably part of the strings are hidden.

Thanks for your reply, @NataliaS.

The strings are all unique. (Our before-uploading-to-Crowdin processing consolidates duplicate strings.) There are 931 unique strings in the file, and Crowdin’s “Source” page shows that, as can be seen in the screenshot above.

I have uploaded (using the v1 API) a version of the file which eliminates some character sequences in the file which were unusual, such as four multi-line (/*...*/) comments which spanned multiple lines (the rest of our comments use /*...*/ but are all on one line), and two instances of /* ... *//* ... */ because I thought the // might have been confusing Crowdin’s parser, but the problem still remains.

The .strings file is UTF-8 and (now) consists of a series of lines like the following:

/* Status message. First '%@' is the name of a configuration. Second '%@' is an IP address (e.g. '') */
"Disconnect All (%@) IP: %@" = "Disconnect All (%1$@) IP: %2$@";

Sixteen of the strings have additional // comments such as:

/* Connection status */
"ADD_ROUTES" = "Adding routes"; // Please translate "Adding routes"

Those 16 strings are all in the range of the 179th through 200th strings (lines 536-599 of the file), so Crowdin isn’t ignoring them, or aborting the parse when it hits them, or anything like that that I can see.

Hi @Tunnelblick ,

Could you please additionally share with us the link to the project to which you uploaded this source file using API v2 ?

@Roman - sure. It’s a private project, so I’m not sure you can access it. Maybe you official Crowdin folks can.

Two files are at Crowdin

  • Localizable.strings is our “production” .strings file, uploaded using the v1 API.
  • Localizable2.160.strings was uploaded using the v2 API.

As I wrote earlier, the files are identical but show a different number of strings and words (and different revision numbers, of course).

If you can’t access the file it I could email it to you (under 200KB); there’s nothing sensitive in it.

Thanks for looking into this.

I have found the problem. The lines like this:

/* Connection status */
"ADD_ROUTES" = "Adding routes"; // Please translate "Adding routes"

confuse the v2 API.

The v1 API isn’t confused by them; it appends the “//” comment to the “/*” comment, as if the lines were something like:

/* Connection status Please translate "Adding routes"*/
"ADD_ROUTES" = "Adding routes";

I edited the file to move the “//” comments into the “/*” comments and uploaded the edited file with v2 successfully: Crowdin displays the file as having “Nothing to translate” (i.e., “the file contains only duplicates or hidden strings”), which is correct because the file has the same strings as the file which was uploaded with the v1 API.

Although the v1 API accepts the “//” comments and processes them reasonably, I can’t find anything that indicates that “//” comments are allowed in .strings files, so I guess it is OK that the v2 API doesn’t accept them, but it would be better if it didn’t get so confused by them.

Hello @Tunnelblick

Hope you’re doing just great and sorry for the late reply, it seems we’ve missed the notification about your response :frowning:

I’m glad to hear that you’ve managed to upload the file successfully!

You’re right about comments, in this file type, we recognize the following structure:
/* comment for string 1 */

More information can be obtained here → iOS Strings file localization

You’re also welcome to test our lightweight API clients, perhaps you’ll find them more comfortable than using a curl option :slight_smile: