API design WTF



  • Through some strange combination of managerial incompetence and "you scratch my back, I scratch yours", we are stuck trying to "integrate" with a metrics/lead generation/email marketing company no one sane has ever heard of.

    My task is simple: Every time someone registers on our site, fire off an API call to add the user on their "contact" list. Each contact has email, name + surname and a custom field with several possible account_types ("STUDENT", "PROFESSOR", that sort of thing).

    You'd think this would be a simple POST request. But...

    Here's the workflow I'll have to follow:

    • POST /contacts, with user name, surname and email. That's the only sane part. It creates/updates the record and gives me back the contact_id.

      Unfortunately, this method does not support the custom account_type field. To update that, I need to access a separate connected record in the field-data resource.

    • PATCH /field-data/{field-data-id} This is the only mention of modifying field-data anywhere in the sparse manual. OK this can help me if I already have a field-data record and I want to update it. But how do I create a new one? Manual doesn't say.

      So, time for postman dart throwing. After a few tries, I find an undocumented POST method that kind of works. But to get there, I now need...

    • GET /fields, to get the list of custom fields in their system. I need that for the field_id.

    • GET /fields/{field_id}/values, to get id-s of available field values. Even though their admin interface allows you to specify a unique "slug" for each option, you can't use that through the API.

    • POST /field-data [ field={field_id}, contact={contact_id}, value={value_id} ] Ok, here we go. That wasn't so bad, was it?

      Oh, wait. If there is already a database row for that specific combination of field and contact, the api just adds a new one. Zero validation or database integrity protection.

      In the admin interface only the original value is displayed. At least nothing visibly broke in the site.

      OK, so I guess I have to find if there's an existing field-data record for a specific contact + field combo. If there is, I use the PATCH method above. If not, I POST a new one.

      So how do I get the existing field data?

      ....

      Oh, no.

    • GET /field-data?query=email@example.com. Yup. Even though everything is connected through foreign keys, you need to search for existing data using a fuzzy search query.

      As expected, some rudimentary testing revealed that if you have contacts mike@a.b and mike@a.b.c, and search for mike@a.b, both records will be matched. So, now I have to...

    • Download the list of field-data items for a fuzzy-match email address. Search through the list in memory and manually match the exact email address with the one from the contact.

      If record is found, use the PATCH query in step 2. If no record is found, use the POST query from step 5.

    • Hope you never ever screw up and create duplicate records that can potentially fuck up their system.

    Now off I go to implement this turd. I just wanted to vent a bit, after spending a whole day figuring this out.


  • Discourse touched me in a no-no place

    @cartman82 said in API design WTF:

    Oh, no.

    *Rubs hands together gleefully*

    This better be good.

    ETA: it was!



  • While I was typing this, their technical lead/salesman/whatever he is responded to my complaints with another half-assed condescending email.

    Here's the entire convo. "dynamics" and "dynamic fields" are what I anonymized to "field data" in the OP. I was trying to be a nice guy, but after blowing me off with my support requests, well, fuck them.

    Me:
    0_1468506592566_upload-3871896c-a91d-4e40-a963-346e64005f65

    Guy:
    0_1468506347425_upload-d5b5bc47-de1c-4f6b-abd1-5ff7ea863744

    Me:
    0_1468506659916_upload-48cf8393-2841-4a35-bc7e-64ee84436bb0

    Guy (the email I just received):
    0_1468506265861_upload-462203a6-0c4a-4a49-96a6-b53ea25c2b96

    Bonus WTF hidden in the api listing above. Shouldn't be too hard to spot.



  • @cartman82 said in API design WTF:

    Bonus WTF hidden in the api listing above. Shouldn't be too hard to spot.

    Oh, I should probably post another excerpt to make it clearer.

    0_1468507290434_upload-34bf74e4-2247-4e7a-9eb7-8fe4eb3eb5eb


  • Garbage Person

    @cartman82 API key as a get parameter. Nice.

    We do that in parts of the WtfFramework integration API, but that's internal only and simply because the incompetent fuckwits we were working with for that section couldn't grok headers or POST.



  • @Weng they are using http, so it's not like that would help either.


  • Garbage Person

    @cartman82 You generally don't copypasta headers or post bodies into IMs, and it's less likely to be logged. It's more an obscurity/accidental leakage prevention than any real security.


  • Winner of the 2016 Presidential Election

    @cartman82 said in API design WTF:

    Bonus WTF hidden in the api listing above. Shouldn't be too hard to spot.

    I thought it was going to be that an unfiltered GET doesn't appear to require an API key.


  • Discourse touched me in a no-no place

    @pydsigner said in API design WTF:

    I thought it was going to be that an unfiltered GET doesn't appear to require an API key.

    Might require a session cookie though, settable by logging in via shibboleth or something like that. Because everything is a web browser!


  • :belt_onion:

    @dkf If you're using a session for auth then why is there an API key required!!!!????/11eleventy/?


  • Discourse touched me in a no-no place

    @sloosecannon It might be an either-or case. Or a stupid belt-and-braces one. I've seen both…


Log in to reply